diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 81127ae7e4..ff3678acac 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,211 +1,212 @@ project (krita-and-all-its-deps) # # Build all dependencies for Krita and finally Krita itself. # Parameters: EXTERNALS_DOWNLOAD_DIR place to download all packages # INSTALL_ROOT place to install everything to # MXE_TOOLCHAIN: the toolchain file to cross-compile using MXE # # Example usage: cmake ..\kritadeposx -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i -DWIN64_BUILD=TRUE -DBOOST_LIBRARYDIR=/dev2/i/lib -G "Visual Studio 11 Win64" cmake_minimum_required(VERSION 2.8.6) if(NOT SUBMAKE_JOBS) set(SUBMAKE_JOBS 1) endif() if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "Compiling in the source directory is not supported. Use for example 'mkdir build; cd build; cmake ..'.") endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) # Tools must be obtained to work with: include (ExternalProject) # allow specification of a directory with pre-downloaded # requirements if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR}) message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR") endif() if(NOT IS_DIRECTORY ${INSTALL_ROOT}) message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT") endif() set(TOP_INST_DIR ${INSTALL_ROOT}) set(EXTPREFIX "${TOP_INST_DIR}") set(CMAKE_PREFIX_PATH "${EXTPREFIX}") if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") SET(GLOBAL_PROFILE -DCMAKE_MODULE_LINKER_FLAGS=/machine:x64 -DCMAKE_EXE_LINKER_FLAGS=/machine:x64 -DCMAKE_SHARED_LINKER_FLAGS=/machine:x64 -DCMAKE_STATIC_LINKER_FLAGS=/machine:x64 ) endif () message( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") message( STATUS "CMAKE_CL_64: ${CMAKE_CL_64}") set(GLOBAL_BUILD_TYPE RelWithDebInfo) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DBUILD_TESTING=false) if (MINGW) option(QT_ENABLE_DEBUG_INFO "Build Qt with debug info included" OFF) option(QT_ENABLE_DYNAMIC_OPENGL "Build Qt with dynamic ANGLE support '-opengl dynamic -angle' (needs env var 'WindowsSdkDir' set to path of Windows 10 SDK)" ON) if (QT_ENABLE_DYNAMIC_OPENGL) if (DEFINED ENV{WindowsSdkDir}) message(STATUS "WindowsSdkDir is set to '$ENV{WindowsSdkDir}'") else (DEFINED ENV{WindowsSdkDir}) message(FATAL_ERROR "Environment variable 'WindowsSdkDir' not set! Please set it to path of Windows 10 SDK or disable QT_ENABLE_DYNAMIC_OPENGL") endif () endif () endif (MINGW) set(SECURITY_EXE_LINKER_FLAGS "") set(SECURITY_SHARED_LINKER_FLAGS "") set(SECURITY_MODULE_LINKER_FLAGS "") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) if (USE_MINGW_HARDENING_LINKER) set(SECURITY_EXE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(SECURITY_SHARED_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(SECURITY_MODULE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(SECURITY_EXE_LINKER_FLAGS "${SECURITY_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(SECURITY_SHARED_LINKER_FLAGS "${SECURITY_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(SECURITY_MODULE_LINKER_FLAGS "${SECURITY_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=${SECURITY_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS=${SECURITY_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS=${SECURITY_MODULE_LINKER_FLAGS} ) endif () else () message(WARNING "Linker Security Flags not enabled!") endif () endif () if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif () if (MSVC) message(FATAL_ERROR "Krita cannot be built with MSVC. See the README.md file!") endif() if (MINGW) set(PATCH_COMMAND myptch) endif() if (MSYS) set(PATCH_COMMAND patch) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN} -DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib -DZLIB_ROOT=${CMAKE_PREFIX_PATH} ) set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 ) endif() if (APPLE) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON) set(PATCH_COMMAND patch) endif () if (UNIX AND NOT APPLE) set(LINUX true) set(PATCH_COMMAND patch) endif () function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif () unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if (MINGW) option(ENABLE_PYTHON_DEPS "Enable Python deps (sip, pyqt)" ON) if (ENABLE_PYTHON_DEPS) if (ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif() if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) message(STATUS "Python requirements met.") TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct!") endif () else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.") endif () endif () endif () # this list must be dependency-ordered if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_python ) endif () if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif () add_subdirectory( ext_lzma ) add_subdirectory( ext_iconv ) add_subdirectory( ext_gettext ) add_subdirectory( ext_zlib ) add_subdirectory( ext_boost ) add_subdirectory( ext_jpeg ) add_subdirectory( ext_tiff ) add_subdirectory( ext_png ) add_subdirectory( ext_eigen3 ) add_subdirectory( ext_expat ) # for exiv2 add_subdirectory( ext_exiv2 ) add_subdirectory( ext_ilmbase ) add_subdirectory( ext_lcms2 ) add_subdirectory( ext_openexr ) +add_subdirectory( ext_openssl ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) add_subdirectory( ext_fontconfig) add_subdirectory( ext_freetype) add_subdirectory( ext_qt ) add_subdirectory( ext_poppler ) add_subdirectory( ext_libraw ) add_subdirectory( ext_frameworks ) if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_sip ) add_subdirectory( ext_pyqt ) endif () if (MINGW) add_subdirectory( ext_drmingw ) # add_subdirectory( ext_ffmpeg ) endif () if (NOT APPLE) add_subdirectory( ext_gmic ) endif () add_subdirectory(ext_giflib) add_subdirectory(ext_quazip) diff --git a/3rdparty/ext_openssl/CMakeLists.txt b/3rdparty/ext_openssl/CMakeLists.txt new file mode 100644 index 0000000000..c373526735 --- /dev/null +++ b/3rdparty/ext_openssl/CMakeLists.txt @@ -0,0 +1,17 @@ +SET(PREFIX_ext_openssl "${EXTPREFIX}" ) + +ExternalProject_Add( ext_openssl + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://www.openssl.org/source/openssl-1.1.1b.tar.gz + URL_HASH SHA1=e9710abf5e95c48ebf47991b10cbb48c09dae102 + + CONFIGURE_COMMAND /config --prefix=${PREFIX_ext_openssl} --openssldir=ssl + + BUILD_COMMAND make depend COMMAND make + INSTALL_COMMAND make install + + INSTALL_DIR ${PREFIX_ext_openssl} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_fftw3} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} + + UPDATE_COMMAND "" +) \ No newline at end of file diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 41f664b420..771ac09e7b 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,222 +1,229 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-ssl -no-libproxy -no-system-proxies -no-icu -no-mtdev -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard # -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg # - -opensource -confirm-license + -opensource -confirm-license -openssl-linked # -release -platform win32-g++ -prefix ${EXTPREFIX_qt} QMAKE_LFLAGS_APP+=${SECURITY_EXE_LINKER_FLAGS} QMAKE_LFLAGS_SHLIB+=${SECURITY_SHARED_LINKER_FLAGS} QMAKE_LFLAGS_SONAME+=${SECURITY_SHARED_LINKER_FLAGS} ) if (QT_ENABLE_DEBUG_INFO) # Set the option to build Qt with debugging info enabled list(APPEND _QT_conf -force-debug-info) endif(QT_ENABLE_DEBUG_INFO) if (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl dynamic -angle) else (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl desktop -no-angle) endif (QT_ENABLE_DYNAMIC_OPENGL) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.1/single/qt-everywhere-src-5.12.1.tar.xz URL_HASH SHA1=018b7467faeda9979fe6b4a435214903c3a20018 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Hack-always-return-we-support-DIBV5.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Fix-debug-on-openGL-ES.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-cumulative-patch-for-hdr.patch + CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' + INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat ${_QT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" # Use a short name to reduce the chance of exceeding path length limit SOURCE_DIR s BINARY_DIR b DEPENDS ext_patch ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.1/single/qt-everywhere-src-5.12.1.tar.xz URL_HASH SHA1=018b7467faeda9979fe6b4a435214903c3a20018 URL_MD5 c5e275ab0ed7ee61d0f4b82cd471770d PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0010-Fix-tablet-jitter-on-X11.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-when-QTabletEvent-is-acc.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch - CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard --skip qtmultimedia + CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' + + CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -openssl-linked -verbose -nomake examples -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) else( APPLE ) # XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need # to detect it manually here. if (NOT XCODE_VERSION) execute_process( COMMAND xcodebuild -version OUTPUT_VARIABLE xcodebuild_version OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_FILE /dev/null ) string(REGEX MATCH "Xcode ([0-9]([.][0-9])+)" version_match ${xcodebuild_version}) if (version_match) message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}") set(XCODE_VERSION ${CMAKE_MATCH_1}) else() # If detecting Xcode version failed, set a crazy high version so we default # to the newest. set(XCODE_VERSION 99) message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.") endif(version_match) endif(NOT XCODE_VERSION) # ------------------------------------------------------------------------------- # Verify the Xcode installation on Mac OS like Qt5.7 does/will # If not stop now, the system isn't configured correctly for Qt. # No reason to even proceed. # ------------------------------------------------------------------------------- set(XCSELECT_OUTPUT) find_program(XCSELECT_PROGRAM "xcode-select") if(XCSELECT_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}") set(XCSELECT_COMMAND ${XCSELECT_PROGRAM} "--print-path") execute_process( COMMAND ${XCSELECT_COMMAND} RESULT_VARIABLE XCSELECT_COMMAND_RESULT OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCSELECT_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}") # message(STATUS "${XCSELECT_COMMAND_STR}") message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}") endif() else() message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version") endif() # Belts and suspenders # Beyond all the Xcode and Qt version checking, the proof of the pudding # lies in the success/failure of this command: xcrun --find xcrun. # On failure a patch is necessary, otherwise we're ok # So hard check xcrun now... set(XCRUN_OUTPUT) find_program(XCRUN_PROGRAM "xcrun") if(XCRUN_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}") set(XCRUN_COMMAND ${XCRUN_PROGRAM} "--find xcrun") execute_process( COMMAND ${XCRUN_COMMAND} RESULT_VARIABLE XCRUN_COMMAND_RESULT OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCRUN_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}") # message(STATUS "${XCRUN_COMMAND_STR}") message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}") endif() else() message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}") endif() # # Now configure ext_qt accordingly # if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0))) # Fix Xcode xcrun related issue. # NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9 # NOTE: but no one's holding their breath. set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/mac_standardpaths_qtbug-61159.diff COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-when-QTabletEvent-is-acc.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch #COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch ) message(STATUS "${EXTPREFIX_qt}:Additional patches injected.") else() # No extra patches will be applied # NOTE: defaults for some untested scenarios like xcrun fails and xcode_version < 8. # NOTE: that is uncharted territory and (hopefully) a very unlikely scenario... set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-when-QTabletEvent-is-acc.patch ) endif() # Qt is big - try and parallelize if at all possible include(ProcessorCount) ProcessorCount(NUM_CORES) if(NOT NUM_CORES EQUAL 0) if (NUM_CORES GREATER 2) # be nice... MATH( EXPR NUM_CORES "${NUM_CORES} - 2" ) endif() set(PARALLEL_MAKE "make;-j${NUM_CORES}") message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}") else() set(PARALLEL_MAKE "make") endif() ExternalProject_Add(ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} LOG_DOWNLOAD ON LOG_UPDATE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_TEST ON LOG_INSTALL ON BUILD_IN_SOURCE ON URL https://download.qt.io/archive/qt/5.12/5.12.1/single/qt-everywhere-src-5.12.1.tar.xz URL_HASH MD5=6a37466c8c40e87d4a19c3f286ec2542 + CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' + INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland - -skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -system-zlib -qt-pcre -opensource -confirm-license -prefix ${EXTPREFIX_qt} + -skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -no-sql-sqlite -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -system-zlib -qt-pcre -opensource -confirm-license -openssl-linked -prefix ${EXTPREFIX_qt} + BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/krita/data/templates/dslr/.directory b/krita/data/templates/dslr/.directory index 45a429f165..551fd337e2 100644 --- a/krita/data/templates/dslr/.directory +++ b/krita/data/templates/dslr/.directory @@ -1,40 +1,40 @@ [Desktop Entry] Name=DSLR Templates Name[ar]=قوالب DSLR Name[bs]=DSLR Predlošci -Name[ca]=Plantilles DSLR -Name[ca@valencia]=Plantilles DSLR +Name[ca]=Plantilles de rèflex digitals +Name[ca@valencia]=Plantilles de rèflex digitals Name[cs]=Šablony DSLR Name[da]=DSLR-skabeloner Name[de]=DSLR-Vorlagen Name[el]=Πρότυπα DSLR Name[en_GB]=DSLR Templates Name[es]=Plantillas DSLR Name[et]=Digitaalpeegelkaamera (DSLR) mallid Name[eu]=DSLR txantiloiak Name[fi]=DSLR-pohjat Name[fr]=Modèles DSLR Name[gl]=Modelos DSLR Name[hu]=DSLR sablonok Name[ia]=Patronos de DSLR Name[is]=DSLR sniðmát Name[it]=Modelli DSLR Name[ja]=デジタル一眼レフテンプレート Name[kk]=DSLR үлгілері Name[ko]=DSLR 서식 Name[lt]=DSLR šablonai Name[nb]=DSLR-maler Name[nl]=DSLR-sjablonen Name[pl]=Szablony DSLR Name[pt]=Modelos de DSLR Name[pt_BR]=Modelos DSLR Name[ru]=Шаблоны для фотоаппаратов Name[sk]=Šablóny DSLR Name[sl]=Predloge DSLR Name[sv]=Mallar för digitala spegelreflexkameror Name[tr]=DSLR Şablonları Name[uk]=Шаблони DSLR Name[x-test]=xxDSLR Templatesxx Name[zh_CN]=单反相机模板 Name[zh_TW]=數位單眼相機範本 X-KDE-DefaultTab=true diff --git a/krita/main.cc b/krita/main.cc index 79834617ce..3abe3fbd10 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,486 +1,489 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050900 #include #endif #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #if defined Q_OS_WIN #include #include #include #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if QT_VERSION >= 0x050900 QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", "d3d11"); #endif const QSurfaceFormat format = KisOpenGL::selectSurfaceFormat(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); if (format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } KisOpenGL::setDefaultSurfaceFormat(format); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } // first create the application so we can create a pixmap KisApplication app(key, argc, argv); + + KisUsageLogger::writeHeader(); + if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; #if QT_VERSION >= 0x050900 QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #endif { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index ecf46cc4a3..2fc4853ead 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,269 +1,269 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita — полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

Krita 是全功能的數位藝術工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale målerifiler frå grunnen av.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。

它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

Krita aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta channel floating point 16 dan 32 bit.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, som RGB og CMYK med 8- og 16-bits heiltals- eller flyttalskanalar.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita — отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是绘制概念美术、漫画、纹理和电影布景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。

Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色版中支援 RGB 和 CMYK 等多種色彩空間。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

-

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

-

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

+

Gaudiu pintant amb els motors avançats del pinzell, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

+

Gaudiu pintant amb els motors avançats del pinzell, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

Krita 具有功能强大的笔刷引擎、种类繁多的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。

使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png none none none none none none none none none none none none none none none none none none none none Graphics KDE krita org.kde.krita.desktop
diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp index 8b94443d5e..fb950e376a 100644 --- a/libs/global/KisUsageLogger.cpp +++ b/libs/global/KisUsageLogger.cpp @@ -1,160 +1,164 @@ /* * Copyright (c) 2019 Boudewijn Rempt * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisUsageLogger.h" #include #include #include #include #include #include #include #include #include - +#include #include #include Q_GLOBAL_STATIC(KisUsageLogger, s_instance) const QString KisUsageLogger::s_sectionHeader("================================================================================\n"); struct KisUsageLogger::Private { bool active {false}; QFile logFile; }; KisUsageLogger::KisUsageLogger() : d(new Private) { d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); rotateLog(); d->logFile.open(QFile::Append); - writeHeader(); } KisUsageLogger::~KisUsageLogger() { if (d->active) { close(); } } void KisUsageLogger::initialize() { s_instance->d->active = true; } void KisUsageLogger::close() { log("Closing."); s_instance->d->active = false; s_instance->d->logFile.flush(); s_instance->d->logFile.close(); } void KisUsageLogger::log(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8()); s_instance->d->logFile.write(": "); write(message); } void KisUsageLogger::write(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(message.toUtf8()); s_instance->d->logFile.write("\n"); s_instance->d->logFile.flush(); } void KisUsageLogger::writeHeader() { - Q_ASSERT(d->logFile.isOpen()); + Q_ASSERT(s_instance->d->logFile.isOpen()); + + QString sessionHeader = QString("SESSION: %1. Executing %2\n\n") + .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)) + .arg(qApp->arguments().join(' ')); - QString sessionHeader = QString("SESSION: %1\n\n").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)); QString disclaimer = i18n("WARNING: This file contains information about your system and the\n" "images you have been working with.\n" "\n" "If you have problems with Krita, the Krita developers might ask\n" "you to share this file with them. The information in this file is\n" "not shared automatically with the Krita developers in any way. You\n" "can disable logging to this file in Krita's Configure Krita Dialog.\n" "\n" "Please review the contents of this file before sharing this file with\n" "anyone.\n\n"); QString systemInfo; // NOTE: This is intentionally not translated! // Krita version info systemInfo.append("Krita\n"); - systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); + systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); + systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", ")); + systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false"); systemInfo.append("\n\n"); systemInfo.append("Qt\n"); systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); systemInfo.append("\n Version (loaded): ").append(qVersion()); systemInfo.append("\n\n"); // OS information systemInfo.append("OS Information\n"); systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); systemInfo.append("\n\n"); - d->logFile.write(s_sectionHeader.toUtf8()); - d->logFile.write(sessionHeader.toUtf8()); - d->logFile.write(disclaimer.toUtf8()); - d->logFile.write(systemInfo.toUtf8()); + s_instance->d->logFile.write(s_sectionHeader.toUtf8()); + s_instance->d->logFile.write(sessionHeader.toUtf8()); + s_instance->d->logFile.write(disclaimer.toUtf8()); + s_instance->d->logFile.write(systemInfo.toUtf8()); } void KisUsageLogger::rotateLog() { d->logFile.open(QFile::ReadOnly); QString log = QString::fromUtf8(d->logFile.readAll()); int sectionCount = log.count(s_sectionHeader); int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); while(sectionCount >= s_maxLogs) { log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); sectionCount = log.count(s_sectionHeader); } d->logFile.close(); d->logFile.open(QFile::WriteOnly); d->logFile.write(log.toUtf8()); d->logFile.close(); } diff --git a/libs/global/KisUsageLogger.h b/libs/global/KisUsageLogger.h index 5f128259f6..bf1feda748 100644 --- a/libs/global/KisUsageLogger.h +++ b/libs/global/KisUsageLogger.h @@ -1,61 +1,63 @@ /* * Copyright (c) 2019 Boudewijn Rempt * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISUSAGELOGGER_H #define KISUSAGELOGGER_H #include #include #include "kritaglobal_export.h" /** * @brief The KisUsageLogger class logs messages to a logfile */ class KRITAGLOBAL_EXPORT KisUsageLogger { public: KisUsageLogger(); ~KisUsageLogger(); static void initialize(); static void close(); /// Logs with date/time static void log(const QString &message); /// Writes without date/time static void write(const QString &message); + + static void writeHeader(); + private: - void writeHeader(); void rotateLog(); Q_DISABLE_COPY(KisUsageLogger) struct Private; const QScopedPointer d; static const QString s_sectionHeader; static const int s_maxLogs {10}; }; #endif // KISUSAGELOGGER_H diff --git a/libs/image/3rdparty/lock_free_map/concurrent_map.h b/libs/image/3rdparty/lock_free_map/concurrent_map.h index bb9bddc79c..6671927710 100644 --- a/libs/image/3rdparty/lock_free_map/concurrent_map.h +++ b/libs/image/3rdparty/lock_free_map/concurrent_map.h @@ -1,362 +1,363 @@ /*------------------------------------------------------------------------ Junction: Concurrent data structures in C++ Copyright (c) 2016 Jeff Preshing Distributed under the Simplified BSD License. Original location: https://github.com/preshing/junction This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file for more information. ------------------------------------------------------------------------*/ #ifndef CONCURRENTMAP_H #define CONCURRENTMAP_H #include "leapfrog.h" #include "qsbr.h" template , class VT = DefaultValueTraits > class ConcurrentMap { public: typedef K Key; typedef V Value; typedef KT KeyTraits; typedef VT ValueTraits; typedef quint32 Hash; typedef Leapfrog Details; private: Atomic m_root; QSBR m_gc; public: ConcurrentMap(quint64 capacity = Details::InitialSize) : m_root(Details::Table::create(capacity)) { } ~ConcurrentMap() { typename Details::Table* table = m_root.loadNonatomic(); table->destroy(); + m_gc.flush(); } QSBR &getGC() { return m_gc; } bool migrationInProcess() { return (quint64) m_root.loadNonatomic()->jobCoordinator.loadConsume() != 1; } // publishTableMigration() is called by exactly one thread from Details::TableMigration::run() // after all the threads participating in the migration have completed their work. void publishTableMigration(typename Details::TableMigration* migration) { m_root.store(migration->m_destination, Release); // Caller will GC the TableMigration and the source table. } // A Mutator represents a known cell in the hash table. // It's meant for manipulations within a temporary function scope. // Obviously you must not call QSBR::Update while holding a Mutator. // Any operation that modifies the table (exchangeValue, eraseValue) // may be forced to follow a redirected cell, which changes the Mutator itself. // Note that even if the Mutator was constructed from an existing cell, // exchangeValue() can still trigger a resize if the existing cell was previously marked deleted, // or if another thread deletes the key between the two steps. class Mutator { private: friend class ConcurrentMap; ConcurrentMap& m_map; typename Details::Table* m_table; typename Details::Cell* m_cell; Value m_value; // Constructor: Find existing cell Mutator(ConcurrentMap& map, Key key, bool) : m_map(map), m_value(Value(ValueTraits::NullValue)) { Hash hash = KeyTraits::hash(key); for (;;) { m_table = m_map.m_root.load(Consume); m_cell = Details::find(hash, m_table); if (!m_cell) { return; } Value value = m_cell->value.load(Consume); if (value != Value(ValueTraits::Redirect)) { // Found an existing value m_value = value; return; } // We've encountered a Redirect value. Help finish the migration. m_table->jobCoordinator.participate(); // Try again using the latest root. } } // Constructor: Insert or find cell Mutator(ConcurrentMap& map, Key key) : m_map(map), m_value(Value(ValueTraits::NullValue)) { Hash hash = KeyTraits::hash(key); for (;;) { m_table = m_map.m_root.load(Consume); quint64 overflowIdx; switch (Details::insertOrFind(hash, m_table, m_cell, overflowIdx)) { // Modifies m_cell case Details::InsertResult_InsertedNew: { // We've inserted a new cell. Don't load m_cell->value. return; } case Details::InsertResult_AlreadyFound: { // The hash was already found in the table. Value value = m_cell->value.load(Consume); if (value == Value(ValueTraits::Redirect)) { // We've encountered a Redirect value. break; // Help finish the migration. } // Found an existing value m_value = value; return; } case Details::InsertResult_Overflow: { // Unlike ConcurrentMap_Linear, we don't need to keep track of & pass a "mustDouble" flag. // Passing overflowIdx is sufficient to prevent an infinite loop here. // It defines the start of the range of cells to check while estimating total cells in use. // After the first migration, deleted keys are purged, so if we hit this line during the // second loop iteration, every cell in the range will be in use, thus the estimate will be 100%. // (Concurrent deletes could result in further iterations, but it will eventually settle.) Details::beginTableMigration(m_map, m_table, overflowIdx); break; } } // A migration has been started (either by us, or another thread). Participate until it's complete. m_table->jobCoordinator.participate(); // Try again using the latest root. } } public: Value getValue() const { // Return previously loaded value. Don't load it again. return Value(m_value); } Value exchangeValue(Value desired) { for (;;) { Value oldValue = m_value; if (m_cell->value.compareExchangeStrong(m_value, desired, ConsumeRelease)) { // Exchange was successful. Return previous value. Value result = m_value; m_value = desired; // Leave the mutator in a valid state return result; } // The CAS failed and m_value has been updated with the latest value. if (m_value != Value(ValueTraits::Redirect)) { if (oldValue == Value(ValueTraits::NullValue) && m_value != Value(ValueTraits::NullValue)) { // racing write inserted new value } // There was a racing write (or erase) to this cell. // Pretend we exchanged with ourselves, and just let the racing write win. return desired; } // We've encountered a Redirect value. Help finish the migration. Hash hash = m_cell->hash.load(Relaxed); for (;;) { // Help complete the migration. m_table->jobCoordinator.participate(); // Try again in the new table. m_table = m_map.m_root.load(Consume); m_value = Value(ValueTraits::NullValue); quint64 overflowIdx; switch (Details::insertOrFind(hash, m_table, m_cell, overflowIdx)) { // Modifies m_cell case Details::InsertResult_AlreadyFound: m_value = m_cell->value.load(Consume); if (m_value == Value(ValueTraits::Redirect)) { break; } goto breakOuter; case Details::InsertResult_InsertedNew: goto breakOuter; case Details::InsertResult_Overflow: Details::beginTableMigration(m_map, m_table, overflowIdx); break; } // We were redirected... again } breakOuter:; // Try again in the new table. } } void assignValue(Value desired) { exchangeValue(desired); } Value eraseValue() { for (;;) { if (m_value == Value(ValueTraits::NullValue)) { return Value(m_value); } if (m_cell->value.compareExchangeStrong(m_value, Value(ValueTraits::NullValue), Consume)) { // Exchange was successful and a non-NULL value was erased and returned by reference in m_value. Value result = m_value; m_value = Value(ValueTraits::NullValue); // Leave the mutator in a valid state return result; } // The CAS failed and m_value has been updated with the latest value. if (m_value != Value(ValueTraits::Redirect)) { // There was a racing write (or erase) to this cell. // Pretend we erased nothing, and just let the racing write win. return Value(ValueTraits::NullValue); } // We've been redirected to a new table. Hash hash = m_cell->hash.load(Relaxed); // Re-fetch hash for (;;) { // Help complete the migration. m_table->jobCoordinator.participate(); // Try again in the new table. m_table = m_map.m_root.load(Consume); m_cell = Details::find(hash, m_table); if (!m_cell) { m_value = Value(ValueTraits::NullValue); return m_value; } m_value = m_cell->value.load(Relaxed); if (m_value != Value(ValueTraits::Redirect)) { break; } } } } }; Mutator insertOrFind(Key key) { return Mutator(*this, key); } Mutator find(Key key) { return Mutator(*this, key, false); } // Lookup without creating a temporary Mutator. Value get(Key key) { Hash hash = KeyTraits::hash(key); for (;;) { typename Details::Table* table = m_root.load(Consume); typename Details::Cell* cell = Details::find(hash, table); if (!cell) { return Value(ValueTraits::NullValue); } Value value = cell->value.load(Consume); if (value != Value(ValueTraits::Redirect)) { return value; // Found an existing value } // We've been redirected to a new table. Help with the migration. table->jobCoordinator.participate(); // Try again in the new table. } } Value assign(Key key, Value desired) { Mutator iter(*this, key); return iter.exchangeValue(desired); } Value exchange(Key key, Value desired) { Mutator iter(*this, key); return iter.exchangeValue(desired); } Value erase(Key key) { Mutator iter(*this, key, false); return iter.eraseValue(); } // The easiest way to implement an Iterator is to prevent all Redirects. // The currrent Iterator does that by forbidding concurrent inserts. // To make it work with concurrent inserts, we'd need a way to block TableMigrations. class Iterator { private: typename Details::Table* m_table; quint64 m_idx; Key m_hash; Value m_value; public: Iterator() = default; Iterator(ConcurrentMap& map) { // Since we've forbidden concurrent inserts (for now), nonatomic would suffice here, but let's plan ahead: m_table = map.m_root.load(Consume); m_idx = -1; next(); } void setMap(ConcurrentMap& map) { m_table = map.m_root.load(Consume); m_idx = -1; next(); } void next() { while (++m_idx <= m_table->sizeMask) { // Index still inside range of table. typename Details::CellGroup* group = m_table->getCellGroups() + (m_idx >> 2); typename Details::Cell* cell = group->cells + (m_idx & 3); m_hash = cell->hash.load(Relaxed); if (m_hash != KeyTraits::NullHash) { // Cell has been reserved. m_value = cell->value.load(Relaxed); if (m_value != Value(ValueTraits::NullValue)) return; // Yield this cell. } } // That's the end of the map. m_hash = KeyTraits::NullHash; m_value = Value(ValueTraits::NullValue); } bool isValid() const { return m_value != Value(ValueTraits::NullValue); } Key getKey() const { // Since we've forbidden concurrent inserts (for now), nonatomic would suffice here, but let's plan ahead: return KeyTraits::dehash(m_hash); } Value getValue() const { return m_value; } }; }; #endif // CONCURRENTMAP_LEAPFROG_H diff --git a/libs/image/3rdparty/lock_free_map/qsbr.h b/libs/image/3rdparty/lock_free_map/qsbr.h index 81508780f0..d821cd3b10 100644 --- a/libs/image/3rdparty/lock_free_map/qsbr.h +++ b/libs/image/3rdparty/lock_free_map/qsbr.h @@ -1,109 +1,131 @@ /*------------------------------------------------------------------------ Junction: Concurrent data structures in C++ Copyright (c) 2016 Jeff Preshing Distributed under the Simplified BSD License. Original location: https://github.com/preshing/junction This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file for more information. ------------------------------------------------------------------------*/ #ifndef QSBR_H #define QSBR_H #include #include #include +#include #define CALL_MEMBER(obj, pmf) ((obj).*(pmf)) class QSBR { private: struct Action { void (*func)(void*); quint64 param[4]; // Size limit found experimentally. Verified by assert below. Action() = default; Action(void (*f)(void*), void* p, quint64 paramSize) : func(f) { - Q_ASSERT(paramSize <= sizeof(param)); // Verify size limit. + KIS_ASSERT(paramSize <= sizeof(param)); // Verify size limit. memcpy(¶m, p, paramSize); } void operator()() { func(¶m); } }; - QMutex m_mutex; - QVector m_pendingActions; - QVector m_deferedActions; + QAtomicInt m_rawPointerUsers; + QAtomicInt m_poolSize; + KisLocklessStack m_pendingActions; + KisLocklessStack m_migrationReclaimActions; std::atomic_flag m_isProcessing = ATOMIC_FLAG_INIT; + void releasePoolSafely(KisLocklessStack *pool) { + Action action; + + while (pool->pop(action)) { + action(); + } + } + public: template void enqueue(void (T::*pmf)(), T* target, bool migration = false) { struct Closure { void (T::*pmf)(); T* target; static void thunk(void* param) { Closure* self = (Closure*) param; CALL_MEMBER(*self->target, self->pmf)(); } }; + Closure closure = {pmf, target}; - while (m_isProcessing.test_and_set(std::memory_order_acquire)) { - } if (migration) { - m_deferedActions.append(Action(Closure::thunk, &closure, sizeof(closure))); + m_migrationReclaimActions.push(Action(Closure::thunk, &closure, sizeof(closure))); } else { - m_pendingActions.append(Action(Closure::thunk, &closure, sizeof(closure))); + m_pendingActions.push(Action(Closure::thunk, &closure, sizeof(closure))); } - m_isProcessing.clear(std::memory_order_release); + m_poolSize.ref(); } void update(bool migration) { - if (!m_isProcessing.test_and_set(std::memory_order_acquire)) { - QVector actions; - actions.swap(m_pendingActions); + if (m_rawPointerUsers.testAndSetAcquire(0, 1)) { + releasePoolSafely(&m_pendingActions); + m_poolSize.store(0); if (!migration) { - m_pendingActions.swap(m_deferedActions); + releasePoolSafely(&m_migrationReclaimActions); } - m_isProcessing.clear(std::memory_order_release); + m_rawPointerUsers.deref(); - for (auto &action : actions) { - action(); - } + } else if (m_poolSize > 4098) { + // TODO: make pool size limit configurable! + + while (!m_rawPointerUsers.testAndSetAcquire(0, 1)); + + releasePoolSafely(&m_pendingActions); + m_poolSize.store(0); + + m_rawPointerUsers.deref(); } } void flush() { - if (!m_isProcessing.test_and_set(std::memory_order_acquire)) { - for (auto &action : m_pendingActions) { - action(); - } + while (!m_rawPointerUsers.testAndSetAcquire(0, 1)); - for (auto &action : m_deferedActions) { - action(); - } + releasePoolSafely(&m_pendingActions); + m_poolSize.store(0); - m_isProcessing.clear(std::memory_order_release); - } + releasePoolSafely(&m_migrationReclaimActions); + + m_rawPointerUsers.deref(); + } + + void lockRawPointerAccess() + { + m_rawPointerUsers.ref(); + } + + void unlockRawPointerAccess() + { + m_rawPointerUsers.deref(); } }; #endif // QSBR_H diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 897012aa07..b29e5c9f96 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,3014 +1,3017 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" #include "krita_utils.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #include "kis_painter_p.h" KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const QRect srcExtent = src->extent(); const QRect dstExtent = dst->extent(); const QRect srcSampleRect = srcExtent & srcRect; const QRect dstSampleRect = dstExtent & dstRect; const bool srcEmpty = srcSampleRect.isEmpty(); const bool dstEmpty = dstSampleRect.isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { QRect srcCopyRect = srcRect; QRect dstCopyRect = dstRect; if (!srcExtent.contains(srcRect)) { if (src->defaultPixel() == dst->defaultPixel()) { const QRect dstSampleInSrcCoords = dstSampleRect.translated(srcRect.topLeft() - dstPt); if (dstSampleInSrcCoords.isEmpty() || srcSampleRect.contains(dstSampleInSrcCoords)) { srcCopyRect = srcSampleRect; } else { srcCopyRect = srcSampleRect | dstSampleInSrcCoords; } dstCopyRect = QRect(dstPt + srcCopyRect.topLeft() - srcRect.topLeft(), srcCopyRect.size()); } } KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstCopyRect.topLeft(), src, srcCopyRect); } else { gc.bitBlt(dstCopyRect.topLeft(), src, srcCopyRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); while(it.nextPixel()) { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } void KisPainter::addDirtyRects(const QVector &rects) { d->dirtyRects.reserve(d->dirtyRects.size() + rects.size()); Q_FOREACH (const QRect &rc, rects) { const QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ KIS_SAFE_ASSERT_RECOVER_RETURN(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (const std::bad_alloc&) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } + if (d->strokeStyle == StrokeStyleNone) return; + if (index >= points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], - KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); + KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } + } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: Q_FALLTHROUGH(); case FillStyleGradient: // Currently unsupported Q_FALLTHROUGH(); case FillStyleStrokes: // Currently unsupported warnImage << "Unknown or unsupported fill style in fillPolygon\n"; Q_FALLTHROUGH(); case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() Q_ASSERT(pen.color() == Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = qFloor(start.x()); int y = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KoColor mycolor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = qFloor(fx + 1) - fx; float br2 = fx - qFloor(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = qFloor(fy + 1) - fy; float br2 = fy - qFloor(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KoColor lineColor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = x1; ix2 = x2; iy1 = y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = y1; iy2 = y2; ix1 = x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y integer coordinates xend = x1; yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = x1; iy1 = qFloor(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = x2; yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = x2; iy2 = qFloor(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, qFloor(yf)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, qFloor(yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y integer coordinates yend = y1; xend = x1 + grad * (yend - y1); ygap = y1; ix1 = qFloor(xend); iy1 = y1; // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = y2; xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = qFloor(xend); iy2 = y2; brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(qFloor(xf), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(qFloor(xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - qFloor(yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - qFloor(yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, qFloor(yfa)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, qFloor(yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = qFloor(yfa) + 1; i <= qFloor(yfb); i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(yfa) + 1; i >= qFloor(yfb); i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - qFloor(xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - qFloor(xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(qFloor(xfa), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(qFloor(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = qFloor(xfa) + 1; i <= qFloor(xfb); i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(xfb); i <= qFloor(xfa) + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { // Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPatternSP pattern) { d->pattern = pattern; } const KoPatternSP KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setAverageOpacity(qreal averageOpacity) { d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity); } qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity) { const float exponent = 0.1; return averageOpacity < opacity ? opacity : exponent * opacity + (1.0 - exponent) * (averageOpacity); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradientSP gradient) { d->gradient = gradient; } const KoAbstractGradientSP KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } void KisPainter::copyMirrorInformationFrom(const KisPainter *other) { d->axesCenter = other->d->axesCenter; d->mirrorHorizontally = other->d->mirrorHorizontally; d->mirrorVertically = other->d->mirrorVertically; } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } bool KisPainter::hasHorizontalMirroring() const { return d->mirrorHorizontally; } bool KisPainter::hasVerticalMirroring() const { return d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface) { d->runnableStrokeJobsInterface = interface; } KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const { if (!d->runnableStrokeJobsInterface) { if (!d->fakeRunnableStrokeJobsInterface) { d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor()); } return d->fakeRunnableStrokeJobsInterface.data(); } return d->runnableStrokeJobsInterface; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } bool KisPainter::hasDirtyRegion() const { return !d->dirtyRects.isEmpty(); } void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc); } void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab); } namespace { inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QRect *rc) { KritaUtils::mirrorRect(dir, center, rc); } inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QPointF *pt) { KritaUtils::mirrorPoint(dir, center, pt); } inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QPair *pair) { KritaUtils::mirrorPoint(dir, center, &pair->first); KritaUtils::mirrorPoint(dir, center, &pair->second); } } template QVector KisPainter::Private::calculateMirroredObjects(const T &object) { QVector result; KisLodTransform t(this->device); const QPoint effectiveAxesCenter = t.map(this->axesCenter).toPoint(); T baseObject = object; result << baseObject; if (this->mirrorHorizontally && this->mirrorVertically){ mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject); result << baseObject; mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; } else if (this->mirrorHorizontally) { mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; } else if (this->mirrorVertically) { mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject); result << baseObject; } return result; } const QVector KisPainter::calculateAllMirroredRects(const QRect &rc) { return d->calculateMirroredObjects(rc); } const QVector KisPainter::calculateAllMirroredPoints(const QPointF &pos) { return d->calculateMirroredObjects(pos); } const QVector> KisPainter::calculateAllMirroredPoints(const QPair &pair) { return d->calculateMirroredObjects(pair); } diff --git a/libs/image/tiles3/kis_memento_item.h b/libs/image/tiles3/kis_memento_item.h index 0d1c8c555d..7581d5fece 100644 --- a/libs/image/tiles3/kis_memento_item.h +++ b/libs/image/tiles3/kis_memento_item.h @@ -1,226 +1,236 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_MEMENTO_ITEM_H_ #define KIS_MEMENTO_ITEM_H_ #include #include #include "kis_tile.h" class KisMementoItem; typedef KisSharedPtr KisMementoItemSP; class KisMementoItem : public KisShared { public: enum enumType { CHANGED = 0x0, DELETED = 0x1 }; public: KisMementoItem() : m_tileData(0), m_committedFlag(false) { } KisMementoItem(const KisMementoItem& rhs) : KisShared(), m_tileData(rhs.m_tileData), m_committedFlag(rhs.m_committedFlag), m_type(rhs.m_type), m_col(rhs.m_col), m_row(rhs.m_row), m_next(0), m_parent(0) { if (m_tileData) { if (m_committedFlag) m_tileData->acquire(); else m_tileData->ref(); } } /** * Automatically called by Kis..HashTable. It means that * this mementoItem is a root item of parental hierarchy. * So m_parent should be NULL. Taking into account the tile * was not present before, the status of the item * should be 'DELETED'. * This memmento item is considered as committed, so we acquire * the tile data right at the beginning. */ KisMementoItem(qint32 col, qint32 row, KisTileData* defaultTileData, KisMementoManager *mm) { Q_UNUSED(mm); m_tileData = defaultTileData; /* acquire the tileData deliberately and completely */ m_tileData->acquire(); m_col = col; m_row = row; m_type = DELETED; m_parent = 0; m_committedFlag = true; /* yes, we've committed it */ } /** * FIXME: Not sure this function has any particular usecase. * Just leave it for compatibility with a hash table */ KisMementoItem(const KisMementoItem &rhs, KisMementoManager *mm) { Q_UNUSED(mm); m_tileData = rhs.m_tileData; /* Setting counter: m_refCount++ */ m_tileData->ref(); m_col = rhs.m_col; m_row = rhs.m_row; m_type = CHANGED; m_parent = 0; m_committedFlag = false; } ~KisMementoItem() { releaseTileData(); } - void notifyDead() { - // just to resemple KisTile... + void notifyDetachedFromDataManager() { + // just to resemble KisTile... } + void notifyDeadWithoutDetaching() { + // just to resemble KisTile... + } + + void notifyAttachedToDataManager(KisMementoManager *mm) { + Q_UNUSED(mm); + // just to resemble KisTile... + } + + void reset() { releaseTileData(); m_tileData = 0; m_committedFlag = false; } void deleteTile(KisTile* tile, KisTileData* defaultTileData) { m_tileData = defaultTileData; /* Setting counter: m_refCount++ */ m_tileData->ref(); m_col = tile->col(); m_row = tile->row(); m_type = DELETED; } void changeTile(KisTile* tile) { m_tileData = tile->tileData(); /* Setting counter: m_refCount++ */ m_tileData->ref(); m_col = tile->col(); m_row = tile->row(); m_type = CHANGED; } void commit() { if (m_committedFlag) return; if (m_tileData) { /** * Setting counters to proper values: * m_refCount++, m_usersCount++; * m_refCount-- */ m_tileData->acquire(); m_tileData->deref(); m_tileData->setMementoed(true); } m_committedFlag = true; } inline KisTileSP tile(KisMementoManager *mm) { Q_ASSERT(m_tileData); return KisTileSP(new KisTile(m_col, m_row, m_tileData, mm)); } inline enumType type() { return m_type; } inline void setParent(KisMementoItemSP parent) { m_parent = parent; } inline KisMementoItemSP parent() { return m_parent; } // Stuff for Kis..HashTable inline void setNext(KisMementoItemSP next) { m_next = next; } inline KisMementoItemSP next() const { return m_next; } inline qint32 col() const { return m_col; } inline qint32 row() const { return m_row; } inline KisTileData* tileData() const { return m_tileData; } void debugPrintInfo() { QString s = QString("------\n" "Memento item:\t\t0x%1 (0x%2)\n" " status:\t(%3,%4) %5%6\n" " parent:\t0x%7 (0x%8)\n" " next:\t0x%9 (0x%10)\n") .arg((quintptr)this) .arg((quintptr)m_tileData) .arg(m_col) .arg(m_row) .arg((m_type == CHANGED) ? 'W' : 'D') .arg(m_committedFlag ? 'C' : '-') .arg((quintptr)m_parent.data()) .arg(m_parent ? (quintptr)m_parent->m_tileData : 0) .arg((quintptr)m_next.data()) .arg(m_next ? (quintptr)m_next->m_tileData : 0); dbgKrita << s; } protected: void releaseTileData() { if (m_tileData) { if (m_committedFlag) { m_tileData->setMementoed(false); m_tileData->release(); } else { m_tileData->deref(); } } } protected: KisTileData *m_tileData; bool m_committedFlag; enumType m_type; qint32 m_col; qint32 m_row; KisMementoItemSP m_next; KisMementoItemSP m_parent; private: }; #endif /* KIS_MEMENTO_ITEM_H_ */ diff --git a/libs/image/tiles3/kis_tile.cc b/libs/image/tiles3/kis_tile.cc index a8397fece8..b67496ca77 100644 --- a/libs/image/tiles3/kis_tile.cc +++ b/libs/image/tiles3/kis_tile.cc @@ -1,233 +1,255 @@ /* * Copyright (c) 2002 Patrick Julien * (c) 2009 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //#define DEAD_TILES_SANITY_CHECK #include "kis_tile_data.h" #include "kis_tile_data_store.h" #include "kis_tile.h" #include "kis_memento_manager.h" #include "kis_debug.h" void KisTile::init(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm) { m_col = col; m_row = row; m_lockCounter = 0; m_extent = QRect(m_col * KisTileData::WIDTH, m_row * KisTileData::HEIGHT, KisTileData::WIDTH, KisTileData::HEIGHT); m_tileData = defaultTileData; m_tileData->acquire(); - m_mementoManager = mm; - - if (m_mementoManager) - m_mementoManager->registerTileChange(this); + if (mm) { + mm->registerTileChange(this); + } + m_mementoManager.storeRelease(mm); } KisTile::KisTile(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm) { init(col, row, defaultTileData, mm); } KisTile::KisTile(const KisTile& rhs, qint32 col, qint32 row, KisMementoManager* mm) : KisShared() { init(col, row, rhs.tileData(), mm); } KisTile::KisTile(const KisTile& rhs, KisMementoManager* mm) : KisShared() { init(rhs.col(), rhs.row(), rhs.tileData(), mm); } KisTile::KisTile(const KisTile& rhs) : KisShared() { init(rhs.col(), rhs.row(), rhs.tileData(), rhs.m_mementoManager); } KisTile::~KisTile() { Q_ASSERT(!m_lockCounter); #ifdef DEAD_TILES_SANITY_CHECK /** - * We should have been disconnected from the memento manager in notifyDead(). + * We should have been disconnected from the memento manager in notifyDetachedFromDataManager(). * otherwise, there is a bug */ Q_ASSERT(!m_mementoManager); #endif m_tileData->release(); } -void KisTile::notifyDead() +void KisTile::notifyDetachedFromDataManager() { - if (m_mementoManager) { + if (m_mementoManager.loadAcquire()) { KisMementoManager *manager = m_mementoManager; - m_mementoManager = 0; + m_mementoManager.storeRelease(0); manager->registerTileDeleted(this); } } +void KisTile::notifyDeadWithoutDetaching() +{ + m_mementoManager.storeRelease(0); +} + +void KisTile::notifyAttachedToDataManager(KisMementoManager *mm) +{ + if (!m_mementoManager.loadAcquire()) { + QMutexLocker locker(&m_COWMutex); + + if (!m_mementoManager.loadAcquire()) { + + if (mm) { + mm->registerTileChange(this); + } + m_mementoManager.storeRelease(mm); + } + } +} + //#define DEBUG_TILE_LOCKING //#define DEBUG_TILE_COWING #ifdef DEBUG_TILE_LOCKING #define DEBUG_LOG_ACTION(action) \ printf("### %s \ttile:\t0x%llX (%d, %d) (0x%llX) ###\n", action, (quintptr)this, m_col, m_row, (quintptr)m_tileData) #else #define DEBUG_LOG_ACTION(action) #endif #ifdef DEBUG_TILE_COWING #define DEBUG_COWING(newTD) \ printf("### COW done \ttile:\t0x%X (%d, %d) (0x%X -> 0x%X) [mm: 0x%X] ###\n", (quintptr)this, m_col, m_row, (quintptr)m_tileData, (quintptr)newTD, m_mementoManager); #else #define DEBUG_COWING(newTD) #endif inline void KisTile::blockSwapping() const { /** * We need to hold a specal barrier lock here to ensure * m_tileData->blockSwapping() has finished executing * before anyone started reading the tile data. That is * why we can not use atomic operations here. */ QMutexLocker locker(&m_swapBarrierLock); Q_ASSERT(m_lockCounter >= 0); if(!m_lockCounter++) m_tileData->blockSwapping(); Q_ASSERT(data()); } inline void KisTile::unblockSwapping() const { QMutexLocker locker(&m_swapBarrierLock); Q_ASSERT(m_lockCounter > 0); if(--m_lockCounter == 0) { m_tileData->unblockSwapping(); if(!m_oldTileData.isEmpty()) { Q_FOREACH (KisTileData *td, m_oldTileData) { td->unblockSwapping(); td->release(); } m_oldTileData.clear(); } } } inline void KisTile::safeReleaseOldTileData(KisTileData *td) { QMutexLocker locker(&m_swapBarrierLock); Q_ASSERT(m_lockCounter >= 0); if(m_lockCounter > 0) { m_oldTileData.push(td); } else { td->unblockSwapping(); td->release(); } } void KisTile::lockForRead() const { DEBUG_LOG_ACTION("lock [R]"); blockSwapping(); } #define lazyCopying() (m_tileData->m_usersCount>1) void KisTile::lockForWrite() { blockSwapping(); /* We are doing COW here */ if (lazyCopying()) { m_COWMutex.lock(); /** * Everything could have happened before we took * the mutex, so let's check again... */ if (lazyCopying()) { KisTileData *tileData = m_tileData->clone(); tileData->acquire(); tileData->blockSwapping(); KisTileData *oldTileData = m_tileData; m_tileData = tileData; safeReleaseOldTileData(oldTileData); DEBUG_COWING(tileData); - if (m_mementoManager) - m_mementoManager->registerTileChange(this); + KisMementoManager *mm = m_mementoManager.load(); + if (mm) { + mm->registerTileChange(this); + } } m_COWMutex.unlock(); } DEBUG_LOG_ACTION("lock [W]"); } void KisTile::unlock() const { unblockSwapping(); DEBUG_LOG_ACTION("unlock"); } #include void KisTile::debugPrintInfo() { dbgTiles << "------\n" "Tile:\t\t\t" << this << "\n data:\t" << m_tileData << "\n next:\t" << m_nextTile.data(); } void KisTile::debugDumpTile() { lockForRead(); quint8 *data = this->data(); for (int i = 0; i < KisTileData::HEIGHT; i++) { for (int j = 0; j < KisTileData::WIDTH; j++) { dbgTiles << data[(i*KisTileData::WIDTH+j)*pixelSize()]; } } unlock(); } diff --git a/libs/image/tiles3/kis_tile.h b/libs/image/tiles3/kis_tile.h index 7f3a34bbd5..33977e0873 100644 --- a/libs/image/tiles3/kis_tile.h +++ b/libs/image/tiles3/kis_tile.h @@ -1,166 +1,175 @@ /* * Copyright (c) 2004 C. Boemann * (c) 2009 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TILE_H_ #define KIS_TILE_H_ #include #include +#include #include #include #include #include #include "kis_tile_data.h" #include "kis_tile_data_store.h" class KisTile; typedef KisSharedPtr KisTileSP; class KisMementoManager; /** * Provides abstraction to a tile. * + A tile contains a part of a PaintDevice, * but only the individual pixels are accesable * and that only via iterators. * + Actual tile data is stored in KisTileData that can be * shared between many tiles */ class KRITAIMAGE_EXPORT KisTile : public KisShared { public: KisTile(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm); KisTile(const KisTile& rhs, qint32 col, qint32 row, KisMementoManager* mm); KisTile(const KisTile& rhs, KisMementoManager* mm); KisTile(const KisTile& rhs); ~KisTile(); /** * This method is called by the hash table when the tile is * disconnected from it. It means that from now on the tile is not * associated with any particular datamanager. All the users of * the tile (via shared pointers) may silently finish they work on * this tile and leave it. No result will be saved. Used for * threading purposes */ - void notifyDead(); + void notifyDetachedFromDataManager(); + + /** + * Sometimes the tile gets replaced with another tile. In this case + * we shouldn't notify memento manager that the tile has died. Just + * forget the link to the manager and bury it in peace. + */ + void notifyDeadWithoutDetaching(); + + /** + * Called by the hash table to notify that the tile has been attached + * to the data manager. + */ + void notifyAttachedToDataManager(KisMementoManager *mm); public: void debugPrintInfo(); void debugDumpTile(); void lockForRead() const; void lockForWrite(); void unlock() const; /* this allows us work directly on tile's data */ inline quint8 *data() const { return m_tileData->data(); } inline void setData(const quint8 *data) { m_tileData->setData(data); } inline qint32 row() const { return m_row; } inline qint32 col() const { return m_col; } inline QRect extent() const { return m_extent; //QRect(m_col * KisTileData::WIDTH, m_row * KisTileData::HEIGHT, // KisTileData::WIDTH, KisTileData::HEIGHT); } inline KisTileSP next() const { return m_nextTile; } void setNext(KisTileSP next) { m_nextTile = next; } inline qint32 pixelSize() const { /* don't lock here as pixelSize is constant */ return m_tileData->pixelSize(); } inline KisTileData* tileData() const { return m_tileData; } private: void init(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm); inline void blockSwapping() const; inline void unblockSwapping() const; inline void safeReleaseOldTileData(KisTileData *td); private: KisTileData *m_tileData; mutable QStack m_oldTileData; mutable volatile int m_lockCounter; qint32 m_col; qint32 m_row; /** * Added for faster retrieving by processors */ QRect m_extent; /** * For KisTiledDataManager's hash table */ KisTileSP m_nextTile; -#ifdef DEAD_TILES_SANITY_CHECK QAtomicPointer m_mementoManager; -#else - KisMementoManager *m_mementoManager; -#endif - /** * This is a special mutex for guarding copy-on-write * operations. We do not use lockless way here as it'll * create too much overhead for the most common operations * like "read the pointer of m_tileData". */ QMutex m_COWMutex; /** * This lock is used to ensure no one will read the tile data * before it has been loaded from to the memory. */ mutable QMutex m_swapBarrierLock; }; #endif // KIS_TILE_H_ diff --git a/libs/image/tiles3/kis_tile_hash_table2.h b/libs/image/tiles3/kis_tile_hash_table2.h index 8b059e16b7..06ea6f5def 100644 --- a/libs/image/tiles3/kis_tile_hash_table2.h +++ b/libs/image/tiles3/kis_tile_hash_table2.h @@ -1,429 +1,449 @@ /* * Copyright (c) 2018 Andrey Kamakin * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TILEHASHTABLE_2_H #define KIS_TILEHASHTABLE_2_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "3rdparty/lock_free_map/concurrent_map.h" #include "kis_tile.h" #include "kis_debug.h" #define SANITY_CHECK /** * This is a template for a hash table that stores tiles (or some other * objects resembling tiles). Actually, this object should only have - * col()/row() methods and be able to answer notifyDead() requests to + * col()/row() methods and be able to answer notifyDetachedFromDataManager() requests to * be stored here. It is used in KisTiledDataManager and * KisMementoManager. * * How to use: * 1) each hash must be unique, otherwise tiles would rewrite each-other * 2) 0 key is reserved, so can't be used * 3) col and row must be less than 0x7FFF to guarantee uniqueness of hash for each pair */ template class KisTileHashTableIteratorTraits2; template class KisTileHashTableTraits2 { static constexpr bool isInherited = std::is_convertible::value; Q_STATIC_ASSERT_X(isInherited, "Template must inherit KisShared"); public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef KisWeakSharedPtr TileTypeWSP; KisTileHashTableTraits2(KisMementoManager *mm); KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm); ~KisTileHashTableTraits2(); bool isEmpty() { return !m_numTiles.load(); } bool tileExists(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * returns null. * \param col column of the tile * \param row row of the tile */ TileTypeSP getExistingTile(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * creates a new one, attaches it to the list and returns. * \param col column of the tile * \param row row of the tile * \param newTile out-parameter, returns true if a new tile * was created */ TileTypeSP getTileLazy(qint32 col, qint32 row, bool& newTile); /** * Returns a tile in position (col,row). If no tile exists, * creates nothing, but returns shared default tile object * of the table. Be careful, this object has column and row * parameters set to (qint32_MIN, qint32_MIN). * \param col column of the tile * \param row row of the tile * \param existingTile returns true if the tile actually exists in the table * and it is not a lazily created default wrapper tile */ TileTypeSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile); void addTile(TileTypeSP tile); bool deleteTile(TileTypeSP tile); bool deleteTile(qint32 col, qint32 row); void clear(); void setDefaultTileData(KisTileData *defaultTileData); KisTileData* defaultTileData(); qint32 numTiles() { return m_numTiles.load(); } void debugPrintInfo(); void debugMaxListLength(qint32 &min, qint32 &max); friend class KisTileHashTableIteratorTraits2; private: struct MemoryReclaimer { MemoryReclaimer(TileType *data) : d(data) {} void destroy() { - d->notifyDead(); TileTypeSP::deref(reinterpret_cast(this), d); - this->MemoryReclaimer::~MemoryReclaimer(); delete this; } private: TileType *d; }; inline quint32 calculateHash(qint32 col, qint32 row) { #ifdef SANITY_CHECK KIS_ASSERT_RECOVER_NOOP(row < 0x7FFF && col < 0x7FFF) #endif // SANITY_CHECK if (col == 0 && row == 0) { col = 0x7FFF; row = 0x7FFF; } return ((static_cast(row) << 16) | (static_cast(col) & 0xFFFF)); } inline void insert(quint32 idx, TileTypeSP item) { TileTypeSP::ref(&item, item.data()); TileType *tile = 0; { QReadLocker locker(&m_iteratorLock); + m_map.getGC().lockRawPointerAccess(); tile = m_map.assign(idx, item.data()); } if (tile) { + tile->notifyDeadWithoutDetaching(); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile)); } else { m_numTiles.fetchAndAddRelaxed(1); } + m_map.getGC().unlockRawPointerAccess(); + m_map.getGC().update(m_map.migrationInProcess()); } inline bool erase(quint32 idx) { + m_map.getGC().lockRawPointerAccess(); + bool wasDeleted = false; TileType *tile = m_map.erase(idx); if (tile) { + tile->notifyDetachedFromDataManager(); + wasDeleted = true; m_numTiles.fetchAndSubRelaxed(1); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile)); } + m_map.getGC().unlockRawPointerAccess(); + m_map.getGC().update(m_map.migrationInProcess()); return wasDeleted; } private: mutable ConcurrentMap m_map; /** * We still need something to guard changes in m_defaultTileData, * otherwise there will be concurrent read/writes, resulting in broken memory. */ QReadWriteLock m_defaultPixelDataLock; mutable QReadWriteLock m_iteratorLock; - std::atomic_flag m_lazyLock = ATOMIC_FLAG_INIT; QAtomicInt m_numTiles; KisTileData *m_defaultTileData; KisMementoManager *m_mementoManager; }; template class KisTileHashTableIteratorTraits2 { public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef typename ConcurrentMap::Iterator Iterator; KisTileHashTableIteratorTraits2(KisTileHashTableTraits2 *ht) : m_ht(ht) { m_ht->m_iteratorLock.lockForWrite(); m_iter.setMap(m_ht->m_map); } ~KisTileHashTableIteratorTraits2() { m_ht->m_iteratorLock.unlock(); } void next() { m_iter.next(); } TileTypeSP tile() const { return TileTypeSP(m_iter.getValue()); } bool isDone() const { return !m_iter.isValid(); } void deleteCurrent() { m_ht->erase(m_iter.getKey()); next(); } void moveCurrentToHashTable(KisTileHashTableTraits2 *newHashTable) { TileTypeSP tile = m_iter.getValue(); next(); quint32 idx = m_ht->calculateHash(tile->col(), tile->row()); m_ht->erase(idx); newHashTable->insert(idx, tile); } private: KisTileHashTableTraits2 *m_ht; Iterator m_iter; }; template KisTileHashTableTraits2::KisTileHashTableTraits2(KisMementoManager *mm) : m_numTiles(0), m_defaultTileData(0), m_mementoManager(mm) { } template KisTileHashTableTraits2::KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm) : KisTileHashTableTraits2(mm) { setDefaultTileData(ht.m_defaultTileData); QWriteLocker locker(&ht.m_iteratorLock); typename ConcurrentMap::Iterator iter(ht.m_map); while (iter.isValid()) { TileTypeSP tile = new TileType(*iter.getValue(), m_mementoManager); insert(iter.getKey(), tile); iter.next(); } } template KisTileHashTableTraits2::~KisTileHashTableTraits2() { clear(); setDefaultTileData(0); } template bool KisTileHashTableTraits2::tileExists(qint32 col, qint32 row) { return getExistingTile(col, row); } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getExistingTile(qint32 col, qint32 row) { quint32 idx = calculateHash(col, row); + + m_map.getGC().lockRawPointerAccess(); TileTypeSP tile = m_map.get(idx); + m_map.getGC().unlockRawPointerAccess(); + m_map.getGC().update(m_map.migrationInProcess()); return tile; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getTileLazy(qint32 col, qint32 row, bool &newTile) { newTile = false; quint32 idx = calculateHash(col, row); TileTypeSP tile = m_map.get(idx); if (!tile) { - while (m_lazyLock.test_and_set(std::memory_order_acquire)); - while (!(tile = m_map.get(idx))) { { QReadLocker locker(&m_defaultPixelDataLock); - tile = new TileType(col, row, m_defaultTileData, m_mementoManager); + tile = new TileType(col, row, m_defaultTileData, 0); } TileTypeSP::ref(&tile, tile.data()); TileType *item = 0; { QReadLocker locker(&m_iteratorLock); + m_map.getGC().lockRawPointerAccess(); item = m_map.assign(idx, tile.data()); } if (item) { + item->notifyDeadWithoutDetaching(); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(item)); } else { newTile = true; m_numTiles.fetchAndAddRelaxed(1); } - } - m_lazyLock.clear(std::memory_order_release); + m_map.getGC().unlockRawPointerAccess(); + } } + tile->notifyAttachedToDataManager(m_mementoManager); + m_map.getGC().update(m_map.migrationInProcess()); return tile; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) { quint32 idx = calculateHash(col, row); + + m_map.getGC().lockRawPointerAccess(); TileTypeSP tile = m_map.get(idx); + m_map.getGC().unlockRawPointerAccess(); + existingTile = tile; if (!existingTile) { QReadLocker locker(&m_defaultPixelDataLock); tile = new TileType(col, row, m_defaultTileData, 0); } m_map.getGC().update(m_map.migrationInProcess()); return tile; } template void KisTileHashTableTraits2::addTile(TileTypeSP tile) { quint32 idx = calculateHash(tile->col(), tile->row()); insert(idx, tile); } template bool KisTileHashTableTraits2::deleteTile(TileTypeSP tile) { return deleteTile(tile->col(), tile->row()); } template bool KisTileHashTableTraits2::deleteTile(qint32 col, qint32 row) { quint32 idx = calculateHash(col, row); return erase(idx); } template void KisTileHashTableTraits2::clear() { QWriteLocker locker(&m_iteratorLock); typename ConcurrentMap::Iterator iter(m_map); TileType *tile = 0; while (iter.isValid()) { + m_map.getGC().lockRawPointerAccess(); tile = m_map.erase(iter.getKey()); if (tile) { + tile->notifyDetachedFromDataManager(); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile)); } + m_map.getGC().unlockRawPointerAccess(); iter.next(); } m_numTiles.store(0); m_map.getGC().update(false); } template inline void KisTileHashTableTraits2::setDefaultTileData(KisTileData *defaultTileData) { QWriteLocker locker(&m_defaultPixelDataLock); if (m_defaultTileData) { m_defaultTileData->release(); m_defaultTileData = 0; } if (defaultTileData) { defaultTileData->acquire(); m_defaultTileData = defaultTileData; } } template inline KisTileData* KisTileHashTableTraits2::defaultTileData() { QReadLocker locker(&m_defaultPixelDataLock); return m_defaultTileData; } template void KisTileHashTableTraits2::debugPrintInfo() { } template void KisTileHashTableTraits2::debugMaxListLength(qint32 &/*min*/, qint32 &/*max*/) { } typedef KisTileHashTableTraits2 KisTileHashTable; typedef KisTileHashTableIteratorTraits2 KisTileHashTableIterator; typedef KisTileHashTableIteratorTraits2 KisTileHashTableConstIterator; #endif // KIS_TILEHASHTABLE_2_H diff --git a/libs/image/tiles3/kis_tile_hash_table_p.h b/libs/image/tiles3/kis_tile_hash_table_p.h index 59ca4da756..5f5e787f97 100644 --- a/libs/image/tiles3/kis_tile_hash_table_p.h +++ b/libs/image/tiles3/kis_tile_hash_table_p.h @@ -1,446 +1,446 @@ /* * Copyright (c) 2004 C. Boemann * (c) 2009 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_debug.h" #include "kis_global.h" //#define SHARED_TILES_SANITY_CHECK template KisTileHashTableTraits::KisTileHashTableTraits(KisMementoManager *mm) : m_lock(QReadWriteLock::NonRecursive) { m_hashTable = new TileTypeSP [TABLE_SIZE]; Q_CHECK_PTR(m_hashTable); m_numTiles = 0; m_defaultTileData = 0; m_mementoManager = mm; } template KisTileHashTableTraits::KisTileHashTableTraits(const KisTileHashTableTraits &ht, KisMementoManager *mm) : m_lock(QReadWriteLock::NonRecursive) { QReadLocker locker(&ht.m_lock); m_mementoManager = mm; m_defaultTileData = 0; setDefaultTileDataImp(ht.m_defaultTileData); m_hashTable = new TileTypeSP [TABLE_SIZE]; Q_CHECK_PTR(m_hashTable); TileTypeSP foreignTile; TileTypeSP nativeTile; TileTypeSP nativeTileHead; for (qint32 i = 0; i < TABLE_SIZE; i++) { nativeTileHead = 0; foreignTile = ht.m_hashTable[i]; while (foreignTile) { nativeTile = TileTypeSP(new TileType(*foreignTile, m_mementoManager)); nativeTile->setNext(nativeTileHead); nativeTileHead = nativeTile; foreignTile = foreignTile->next(); } m_hashTable[i] = nativeTileHead; } m_numTiles = ht.m_numTiles; } template KisTileHashTableTraits::~KisTileHashTableTraits() { clear(); delete[] m_hashTable; setDefaultTileDataImp(0); } template quint32 KisTileHashTableTraits::calculateHash(qint32 col, qint32 row) { return ((row << 5) + (col & 0x1F)) & 0x3FF; } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getTileMinefieldWalk(qint32 col, qint32 row, qint32 idx) { // WARNING: this function is here only for educational purposes! Don't // use it! It causes race condition in a shared pointer copy-ctor // when accessing m_hashTable! /** * This is a special method for dangerous and unsafe access to * the tiles table. Thanks to the fact that our shared pointers * are thread safe, we can iterate through the linked list without * having any locks help. In the worst case, we will miss the needed * tile. In that case, the higher level code will do the proper * locking and do the second try with all the needed locks held. */ TileTypeSP headTile = m_hashTable[idx]; TileTypeSP tile = headTile; for (; tile; tile = tile->next()) { if (tile->col() == col && tile->row() == row) { if (m_hashTable[idx] != headTile) { tile.clear(); } break; } } return tile; } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getTile(qint32 col, qint32 row, qint32 idx) { TileTypeSP tile = m_hashTable[idx]; for (; tile; tile = tile->next()) { if (tile->col() == col && tile->row() == row) { return tile; } } return TileTypeSP(); } template void KisTileHashTableTraits::linkTile(TileTypeSP tile, qint32 idx) { TileTypeSP firstTile = m_hashTable[idx]; #ifdef SHARED_TILES_SANITY_CHECK Q_ASSERT_X(!tile->next(), "KisTileHashTableTraits::linkTile", "A tile can't be shared by several hash tables, sorry."); #endif tile->setNext(firstTile); m_hashTable[idx] = tile; m_numTiles++; } template bool KisTileHashTableTraits::unlinkTile(qint32 col, qint32 row, qint32 idx) { TileTypeSP tile = m_hashTable[idx]; TileTypeSP prevTile; for (; tile; tile = tile->next()) { if (tile->col() == col && tile->row() == row) { if (prevTile) prevTile->setNext(tile->next()); else /* optimize here*/ m_hashTable[idx] = tile->next(); /** * The shared pointer may still be accessed by someone, so * we need to disconnects the tile from memento manager * explicitly */ tile->setNext(TileTypeSP()); - tile->notifyDead(); + tile->notifyDetachedFromDataManager(); tile.clear(); m_numTiles--; return true; } prevTile = tile; } return false; } template inline void KisTileHashTableTraits::setDefaultTileDataImp(KisTileData *defaultTileData) { if (m_defaultTileData) { m_defaultTileData->release(); m_defaultTileData = 0; } if (defaultTileData) { defaultTileData->acquire(); m_defaultTileData = defaultTileData; } } template inline KisTileData* KisTileHashTableTraits::defaultTileDataImp() const { return m_defaultTileData; } template bool KisTileHashTableTraits::tileExists(qint32 col, qint32 row) { return this->getExisitngTile(col, row); } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getExistingTile(qint32 col, qint32 row) { const qint32 idx = calculateHash(col, row); // NOTE: minefield walk is disabled due to supposed unsafety, // see bug 391270 QReadLocker locker(&m_lock); return getTile(col, row, idx); } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getTileLazy(qint32 col, qint32 row, bool& newTile) { const qint32 idx = calculateHash(col, row); // NOTE: minefield walk is disabled due to supposed unsafety, // see bug 391270 newTile = false; TileTypeSP tile; { QReadLocker locker(&m_lock); tile = getTile(col, row, idx); } if (!tile) { QWriteLocker locker(&m_lock); tile = new TileType(col, row, m_defaultTileData, m_mementoManager); linkTile(tile, idx); newTile = true; } return tile; } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) { const qint32 idx = calculateHash(col, row); // NOTE: minefield walk is disabled due to supposed unsafety, // see bug 391270 QReadLocker locker(&m_lock); TileTypeSP tile = getTile(col, row, idx); existingTile = tile; if (!existingTile) { tile = new TileType(col, row, m_defaultTileData, 0); } return tile; } template void KisTileHashTableTraits::addTile(TileTypeSP tile) { const qint32 idx = calculateHash(tile->col(), tile->row()); QWriteLocker locker(&m_lock); linkTile(tile, idx); } template bool KisTileHashTableTraits::deleteTile(qint32 col, qint32 row) { const qint32 idx = calculateHash(col, row); QWriteLocker locker(&m_lock); return unlinkTile(col, row, idx); } template bool KisTileHashTableTraits::deleteTile(TileTypeSP tile) { return deleteTile(tile->col(), tile->row()); } template void KisTileHashTableTraits::clear() { QWriteLocker locker(&m_lock); TileTypeSP tile = TileTypeSP(); qint32 i; for (i = 0; i < TABLE_SIZE; i++) { tile = m_hashTable[i]; while (tile) { TileTypeSP tmp = tile; tile = tile->next(); /** * About disconnection of tiles see a comment in unlinkTile() */ tmp->setNext(TileTypeSP()); - tmp->notifyDead(); + tmp->notifyDetachedFromDataManager(); tmp = 0; m_numTiles--; } m_hashTable[i] = 0; } Q_ASSERT(!m_numTiles); } template void KisTileHashTableTraits::setDefaultTileData(KisTileData *defaultTileData) { QWriteLocker locker(&m_lock); setDefaultTileDataImp(defaultTileData); } template KisTileData* KisTileHashTableTraits::defaultTileData() const { QWriteLocker locker(&m_lock); return defaultTileDataImp(); } /*************** Debugging stuff ***************/ template void KisTileHashTableTraits::debugPrintInfo() { if (!m_numTiles) return; qInfo() << "==========================\n" << "TileHashTable:" << "\n def. data:\t\t" << m_defaultTileData << "\n numTiles:\t\t" << m_numTiles; debugListLengthDistibution(); qInfo() << "==========================\n"; } template qint32 KisTileHashTableTraits::debugChainLen(qint32 idx) { qint32 len = 0; for (TileTypeSP it = m_hashTable[idx]; it; it = it->next(), len++) ; return len; } template void KisTileHashTableTraits::debugMaxListLength(qint32 &min, qint32 &max) { TileTypeSP tile; qint32 maxLen = 0; qint32 minLen = m_numTiles; qint32 tmp = 0; for (qint32 i = 0; i < TABLE_SIZE; i++) { tmp = debugChainLen(i); if (tmp > maxLen) maxLen = tmp; if (tmp < minLen) minLen = tmp; } min = minLen; max = maxLen; } template void KisTileHashTableTraits::debugListLengthDistibution() { qint32 min, max; qint32 arraySize; qint32 tmp; debugMaxListLength(min, max); arraySize = max - min + 1; qint32 *array = new qint32[arraySize]; memset(array, 0, sizeof(qint32)*arraySize); for (qint32 i = 0; i < TABLE_SIZE; i++) { tmp = debugChainLen(i); array[tmp-min]++; } qInfo() << QString(" minChain: %1\n").arg(min); qInfo() << QString(" maxChain: %1\n").arg(max); qInfo() << " Chain size distribution:"; for (qint32 i = 0; i < arraySize; i++) qInfo() << QString(" %1: %2").arg(i + min).arg(array[i]); delete[] array; } template void KisTileHashTableTraits::sanityChecksumCheck() { /** * We assume that the lock should have already been taken * by the code that was going to change the table */ Q_ASSERT(!m_lock.tryLockForWrite()); TileTypeSP tile = 0; qint32 exactNumTiles = 0; for (qint32 i = 0; i < TABLE_SIZE; i++) { tile = m_hashTable[i]; while (tile) { exactNumTiles++; tile = tile->next(); } } if (exactNumTiles != m_numTiles) { dbgKrita << "Sanity check failed!"; dbgKrita << ppVar(exactNumTiles); dbgKrita << ppVar(m_numTiles); dbgKrita << "Wrong tiles checksum!"; Q_ASSERT(0); // not fatalKrita for a backtrace support } } diff --git a/libs/image/tiles3/kis_tiled_data_manager.cc b/libs/image/tiles3/kis_tiled_data_manager.cc index e7d2143467..cd96eb8bf3 100644 --- a/libs/image/tiles3/kis_tiled_data_manager.cc +++ b/libs/image/tiles3/kis_tiled_data_manager.cc @@ -1,787 +1,788 @@ /* * Copyright (c) 2004 C. Boemann * (c) 2009 Dmitry Kazakov * (c) 2010 Cyrille Berger * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "kis_tile.h" #include "kis_tiled_data_manager.h" #include "kis_tile_data_wrapper.h" #include "kis_tiled_data_manager_p.h" #include "kis_memento_manager.h" #include "swap/kis_legacy_tile_compressor.h" #include "swap/kis_tile_compressor_factory.h" #include "kis_paint_device_writer.h" #include "kis_global.h" /* The data area is divided into tiles each say 64x64 pixels (defined at compiletime) * The tiles are laid out in a matrix that can have negative indexes. * The matrix grows automatically if needed (a call for writeacces to a tile * outside the current extent) * Even though the matrix has grown it may still not contain tiles at specific positions. * They are created on demand */ KisTiledDataManager::KisTiledDataManager(quint32 pixelSize, const quint8 *defaultPixel) { /* See comment in destructor for details */ m_mementoManager = new KisMementoManager(); m_hashTable = new KisTileHashTable(m_mementoManager); m_pixelSize = pixelSize; m_defaultPixel = new quint8[m_pixelSize]; setDefaultPixel(defaultPixel); } KisTiledDataManager::KisTiledDataManager(const KisTiledDataManager &dm) : KisShared() { /* See comment in destructor for details */ /* We do not clone the history of the device, there is no usecase for it */ m_mementoManager = new KisMementoManager(); m_mementoManager->setDefaultTileData(dm.m_hashTable->defaultTileData()); m_hashTable = new KisTileHashTable(*dm.m_hashTable, m_mementoManager); m_pixelSize = dm.m_pixelSize; m_defaultPixel = new quint8[m_pixelSize]; /** * We won't call setDefaultTileData here, as defaultTileDatas * has already been made shared in m_hashTable(dm->m_hashTable) */ memcpy(m_defaultPixel, dm.m_defaultPixel, m_pixelSize); recalculateExtent(); } KisTiledDataManager::~KisTiledDataManager() { /** * Here is an explanation why we use hash table and The Memento Manager * dynamically allocated We need to destroy them in that very order. The * reason is that when hash table destroying all her child tiles they all * cry about it to The Memento Manager using a pointer. So The Memento * Manager should be alive during that destruction. We could use shared * pointers instead, but they create too much overhead. */ delete m_hashTable; delete m_mementoManager; delete[] m_defaultPixel; } void KisTiledDataManager::setDefaultPixel(const quint8 *defaultPixel) { QWriteLocker locker(&m_lock); setDefaultPixelImpl(defaultPixel); } void KisTiledDataManager::setDefaultPixelImpl(const quint8 *defaultPixel) { KisTileData *td = KisTileDataStore::instance()->createDefaultTileData(pixelSize(), defaultPixel); m_hashTable->setDefaultTileData(td); m_mementoManager->setDefaultTileData(td); memcpy(m_defaultPixel, defaultPixel, pixelSize()); } bool KisTiledDataManager::write(KisPaintDeviceWriter &store) { QReadLocker locker(&m_lock); bool retval = true; if(CURRENT_VERSION == LEGACY_VERSION) { char str[80]; sprintf(str, "%d\n", m_hashTable->numTiles()); retval = store.write(str, strlen(str)); } else { retval = writeTilesHeader(store, m_hashTable->numTiles()); } KisTileHashTableConstIterator iter(m_hashTable); KisTileSP tile; KisAbstractTileCompressorSP compressor = KisTileCompressorFactory::create(CURRENT_VERSION); while ((tile = iter.tile())) { retval = compressor->writeTile(tile, store); if (!retval) { warnFile << "Failed to write tile"; break; } iter.next(); } return retval; } bool KisTiledDataManager::read(QIODevice *stream) { clear(); QWriteLocker locker(&m_lock); KisMementoSP nothing = m_mementoManager->getMemento(); if (!stream) { m_mementoManager->commit(); return false; } const qint32 maxLineLength = 79; // Legacy magic QByteArray line = stream->readLine(maxLineLength); line = line.trimmed(); quint32 numTiles; qint32 tilesVersion = LEGACY_VERSION; if (line[0] == 'V') { QList lineItems = line.split(' '); QString keyword = lineItems.takeFirst(); Q_ASSERT(keyword == "VERSION"); tilesVersion = lineItems.takeFirst().toInt(); if(!processTilesHeader(stream, numTiles)) return false; } else { numTiles = line.toUInt(); } KisAbstractTileCompressorSP compressor = KisTileCompressorFactory::create(tilesVersion); bool readSuccess = true; for (quint32 i = 0; i < numTiles; i++) { if (!compressor->readTile(stream, this)) { readSuccess = false; } } m_mementoManager->commit(); return readSuccess; } bool KisTiledDataManager::writeTilesHeader(KisPaintDeviceWriter &store, quint32 numTiles) { QString buffer; buffer = QString("VERSION %1\n" "TILEWIDTH %2\n" "TILEHEIGHT %3\n" "PIXELSIZE %4\n" "DATA %5\n") .arg(CURRENT_VERSION) .arg(KisTileData::WIDTH) .arg(KisTileData::HEIGHT) .arg(pixelSize()) .arg(numTiles); return store.write(buffer.toLatin1()); } #define takeOneLine(stream, maxLine, keyword, value) \ do { \ QByteArray line = stream->readLine(maxLine); \ line = line.trimmed(); \ QList lineItems = line.split(' '); \ keyword = lineItems.takeFirst(); \ value = lineItems.takeFirst().toInt(); \ } while(0) \ bool KisTiledDataManager::processTilesHeader(QIODevice *stream, quint32 &numTiles) { /** * We assume that there is only one version of this header * possible. In case we invent something new, it'll be quite easy * to modify the behavior */ const qint32 maxLineLength = 25; const qint32 totalNumTests = 4; bool foundDataMark = false; qint32 testsPassed = 0; QString keyword; qint32 value; while(!foundDataMark && stream->canReadLine()) { takeOneLine(stream, maxLineLength, keyword, value); if (keyword == "TILEWIDTH") { if(value != KisTileData::WIDTH) goto wrongString; } else if (keyword == "TILEHEIGHT") { if(value != KisTileData::HEIGHT) goto wrongString; } else if (keyword == "PIXELSIZE") { if((quint32)value != pixelSize()) goto wrongString; } else if (keyword == "DATA") { numTiles = value; foundDataMark = true; } else { goto wrongString; } testsPassed++; } if(testsPassed != totalNumTests) { warnTiles << "Not enough fields of tiles header present" << testsPassed << "of" << totalNumTests; } return testsPassed == totalNumTests; wrongString: warnTiles << "Wrong string in tiles header:" << keyword << value; return false; } void KisTiledDataManager::purge(const QRect& area) { QList tilesToDelete; { const qint32 tileDataSize = KisTileData::HEIGHT * KisTileData::WIDTH * pixelSize(); KisTileData *tileData = m_hashTable->defaultTileData(); tileData->blockSwapping(); const quint8 *defaultData = tileData->data(); KisTileHashTableConstIterator iter(m_hashTable); KisTileSP tile; while ((tile = iter.tile())) { if (tile->extent().intersects(area)) { tile->lockForRead(); if(memcmp(defaultData, tile->data(), tileDataSize) == 0) { tilesToDelete.push_back(tile); } tile->unlock(); } iter.next(); } tileData->unblockSwapping(); } Q_FOREACH (KisTileSP tile, tilesToDelete) { - m_extentManager.notifyTileRemoved(tile->col(), tile->row()); - m_hashTable->deleteTile(tile); + if (m_hashTable->deleteTile(tile)) { + m_extentManager.notifyTileRemoved(tile->col(), tile->row()); + } } } quint8* KisTiledDataManager::duplicatePixel(qint32 num, const quint8 *pixel) { const qint32 pixelSize = this->pixelSize(); /* FIXME: Make a fun filling here */ quint8 *dstBuf = new quint8[num * pixelSize]; quint8 *dstIt = dstBuf; for (qint32 i = 0; i < num; i++) { memcpy(dstIt, pixel, pixelSize); dstIt += pixelSize; } return dstBuf; } void KisTiledDataManager::clear(QRect clearRect, const quint8 *clearPixel) { if (clearPixel == 0) clearPixel = m_defaultPixel; if (clearRect.isEmpty()) return; const qint32 pixelSize = this->pixelSize(); bool pixelBytesAreDefault = !memcmp(clearPixel, m_defaultPixel, pixelSize); bool pixelBytesAreTheSame = true; for (qint32 i = 0; i < pixelSize; ++i) { if (clearPixel[i] != clearPixel[0]) { pixelBytesAreTheSame = false; break; } } if (pixelBytesAreDefault) { clearRect &= m_extentManager.extent(); } qint32 firstColumn = xToCol(clearRect.left()); qint32 lastColumn = xToCol(clearRect.right()); qint32 firstRow = yToRow(clearRect.top()); qint32 lastRow = yToRow(clearRect.bottom()); const quint32 rowStride = KisTileData::WIDTH * pixelSize; // Generate one row quint8 *clearPixelData = 0; quint32 maxRunLength = qMin(clearRect.width(), KisTileData::WIDTH); clearPixelData = duplicatePixel(maxRunLength, clearPixel); KisTileData *td = 0; if (!pixelBytesAreDefault && clearRect.width() >= KisTileData::WIDTH && clearRect.height() >= KisTileData::HEIGHT) { td = KisTileDataStore::instance()->createDefaultTileData(pixelSize, clearPixel); td->acquire(); } for (qint32 row = firstRow; row <= lastRow; ++row) { for (qint32 column = firstColumn; column <= lastColumn; ++column) { QRect tileRect(column*KisTileData::WIDTH, row*KisTileData::HEIGHT, KisTileData::WIDTH, KisTileData::HEIGHT); QRect clearTileRect = clearRect & tileRect; if (clearTileRect == tileRect) { // Clear whole tile const bool wasDeleted = m_hashTable->deleteTile(column, row); if (wasDeleted) { m_extentManager.notifyTileRemoved(column, row); } if (!pixelBytesAreDefault) { KisTileSP clearedTile = KisTileSP(new KisTile(column, row, td, m_mementoManager)); m_hashTable->addTile(clearedTile); m_extentManager.notifyTileAdded(column, row); } } else { const qint32 lineSize = clearTileRect.width() * pixelSize; qint32 rowsRemaining = clearTileRect.height(); KisTileDataWrapper tw(this, clearTileRect.left(), clearTileRect.top(), KisTileDataWrapper::WRITE); quint8* tileIt = tw.data(); if (pixelBytesAreTheSame) { while (rowsRemaining > 0) { memset(tileIt, *clearPixelData, lineSize); tileIt += rowStride; rowsRemaining--; } } else { while (rowsRemaining > 0) { memcpy(tileIt, clearPixelData, lineSize); tileIt += rowStride; rowsRemaining--; } } } } } if (td) td->release(); delete[] clearPixelData; } void KisTiledDataManager::clear(QRect clearRect, quint8 clearValue) { quint8 *buf = new quint8[pixelSize()]; memset(buf, clearValue, pixelSize()); clear(clearRect, buf); delete[] buf; } void KisTiledDataManager::clear(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *clearPixel) { clear(QRect(x, y, w, h), clearPixel); } void KisTiledDataManager::clear(qint32 x, qint32 y, qint32 w, qint32 h, quint8 clearValue) { clear(QRect(x, y, w, h), clearValue); } void KisTiledDataManager::clear() { m_hashTable->clear(); m_extentManager.clear(); } template void KisTiledDataManager::bitBltImpl(KisTiledDataManager *srcDM, const QRect &rect) { if (rect.isEmpty()) return; const qint32 pixelSize = this->pixelSize(); const bool defaultPixelsCoincide = !memcmp(srcDM->defaultPixel(), m_defaultPixel, pixelSize); const quint32 rowStride = KisTileData::WIDTH * pixelSize; qint32 firstColumn = xToCol(rect.left()); qint32 lastColumn = xToCol(rect.right()); qint32 firstRow = yToRow(rect.top()); qint32 lastRow = yToRow(rect.bottom()); for (qint32 row = firstRow; row <= lastRow; ++row) { for (qint32 column = firstColumn; column <= lastColumn; ++column) { bool srcTileExists = false; // this is the only variation in the template KisTileSP srcTile = useOldSrcData ? srcDM->getOldTile(column, row, srcTileExists) : srcDM->getReadOnlyTileLazy(column, row, srcTileExists); QRect tileRect(column*KisTileData::WIDTH, row*KisTileData::HEIGHT, KisTileData::WIDTH, KisTileData::HEIGHT); QRect cloneTileRect = rect & tileRect; if (cloneTileRect == tileRect) { // Clone whole tile const bool wasDeleted = m_hashTable->deleteTile(column, row); if (srcTileExists || !defaultPixelsCoincide) { srcTile->lockForRead(); KisTileData *td = srcTile->tileData(); KisTileSP clonedTile = KisTileSP(new KisTile(column, row, td, m_mementoManager)); srcTile->unlock(); m_hashTable->addTile(clonedTile); if (!wasDeleted) { m_extentManager.notifyTileAdded(column, row); } } else if (wasDeleted) { m_extentManager.notifyTileRemoved(column, row); } } else { const qint32 lineSize = cloneTileRect.width() * pixelSize; qint32 rowsRemaining = cloneTileRect.height(); KisTileDataWrapper tw(this, cloneTileRect.left(), cloneTileRect.top(), KisTileDataWrapper::WRITE); srcTile->lockForRead(); // We suppose that the shift in both tiles is the same const quint8* srcTileIt = srcTile->data() + tw.offset(); quint8* dstTileIt = tw.data(); while (rowsRemaining > 0) { memcpy(dstTileIt, srcTileIt, lineSize); srcTileIt += rowStride; dstTileIt += rowStride; rowsRemaining--; } srcTile->unlock(); } } } } template void KisTiledDataManager::bitBltRoughImpl(KisTiledDataManager *srcDM, const QRect &rect) { if (rect.isEmpty()) return; const qint32 pixelSize = this->pixelSize(); const bool defaultPixelsCoincide = !memcmp(srcDM->defaultPixel(), m_defaultPixel, pixelSize); qint32 firstColumn = xToCol(rect.left()); qint32 lastColumn = xToCol(rect.right()); qint32 firstRow = yToRow(rect.top()); qint32 lastRow = yToRow(rect.bottom()); for (qint32 row = firstRow; row <= lastRow; ++row) { for (qint32 column = firstColumn; column <= lastColumn; ++column) { /** * We are cloning whole tiles here so let's not be so boring * to check any borders :) */ bool srcTileExists = false; // this is the only variation in the template KisTileSP srcTile = useOldSrcData ? srcDM->getOldTile(column, row, srcTileExists) : srcDM->getReadOnlyTileLazy(column, row, srcTileExists); const bool wasDeleted = m_hashTable->deleteTile(column, row); if (srcTileExists || !defaultPixelsCoincide) { srcTile->lockForRead(); KisTileData *td = srcTile->tileData(); KisTileSP clonedTile = KisTileSP(new KisTile(column, row, td, m_mementoManager)); srcTile->unlock(); m_hashTable->addTile(clonedTile); if (!wasDeleted) { m_extentManager.notifyTileAdded(column, row); } } else if (wasDeleted) { m_extentManager.notifyTileRemoved(column, row); } } } } void KisTiledDataManager::bitBlt(KisTiledDataManager *srcDM, const QRect &rect) { bitBltImpl(srcDM, rect); } void KisTiledDataManager::bitBltOldData(KisTiledDataManager *srcDM, const QRect &rect) { bitBltImpl(srcDM, rect); } void KisTiledDataManager::bitBltRough(KisTiledDataManager *srcDM, const QRect &rect) { bitBltRoughImpl(srcDM, rect); } void KisTiledDataManager::bitBltRoughOldData(KisTiledDataManager *srcDM, const QRect &rect) { bitBltRoughImpl(srcDM, rect); } void KisTiledDataManager::setExtent(qint32 x, qint32 y, qint32 w, qint32 h) { setExtent(QRect(x, y, w, h)); } void KisTiledDataManager::setExtent(QRect newRect) { QRect oldRect = extent(); newRect = newRect.normalized(); // Do nothing if the desired size is bigger than we currently are: // that is handled by the autoextending automatically if (newRect.contains(oldRect)) return; KisTileSP tile; QRect tileRect; { KisTileHashTableIterator iter(m_hashTable); while (!iter.isDone()) { tile = iter.tile(); tileRect = tile->extent(); if (newRect.contains(tileRect)) { //do nothing iter.next(); } else if (newRect.intersects(tileRect)) { QRect intersection = newRect & tileRect; intersection.translate(- tileRect.topLeft()); const qint32 pixelSize = this->pixelSize(); tile->lockForWrite(); quint8* data = tile->data(); quint8* ptr; /* FIXME: make it faster */ for (int y = 0; y < KisTileData::HEIGHT; y++) { for (int x = 0; x < KisTileData::WIDTH; x++) { if (!intersection.contains(x, y)) { ptr = data + pixelSize * (y * KisTileData::WIDTH + x); memcpy(ptr, m_defaultPixel, pixelSize); } } } tile->unlock(); iter.next(); } else { m_extentManager.notifyTileRemoved(tile->col(), tile->row()); iter.deleteCurrent(); } } } } void KisTiledDataManager::recalculateExtent() { QVector indexes; { KisTileHashTableConstIterator iter(m_hashTable); KisTileSP tile; while ((tile = iter.tile())) { indexes << QPoint(tile->col(), tile->row()); iter.next(); } } m_extentManager.replaceTileStats(indexes); } void KisTiledDataManager::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rect = extent(); rect.getRect(&x, &y, &w, &h); } QRect KisTiledDataManager::extent() const { return m_extentManager.extent(); } QRegion KisTiledDataManager::region() const { QRegion region; KisTileHashTableConstIterator iter(m_hashTable); KisTileSP tile; while ((tile = iter.tile())) { region += tile->extent(); iter.next(); } return region; } void KisTiledDataManager::setPixel(qint32 x, qint32 y, const quint8 * data) { KisTileDataWrapper tw(this, x, y, KisTileDataWrapper::WRITE); memcpy(tw.data(), data, pixelSize()); } void KisTiledDataManager::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 width, qint32 height, qint32 dataRowStride) { QWriteLocker locker(&m_lock); // Actial bytes reading/writing is done in private header writeBytesBody(data, x, y, width, height, dataRowStride); } void KisTiledDataManager::readBytes(quint8 *data, qint32 x, qint32 y, qint32 width, qint32 height, qint32 dataRowStride) const { QReadLocker locker(&m_lock); // Actual bytes reading/writing is done in private header readBytesBody(data, x, y, width, height, dataRowStride); } QVector KisTiledDataManager::readPlanarBytes(QVector channelSizes, qint32 x, qint32 y, qint32 width, qint32 height) const { QReadLocker locker(&m_lock); // Actial bytes reading/writing is done in private header return readPlanarBytesBody(channelSizes, x, y, width, height); } void KisTiledDataManager::writePlanarBytes(QVector planes, QVector channelSizes, qint32 x, qint32 y, qint32 width, qint32 height) { QWriteLocker locker(&m_lock); // Actial bytes reading/writing is done in private header bool allChannelsPresent = true; Q_FOREACH (const quint8* plane, planes) { if (!plane) { allChannelsPresent = false; break; } } if (allChannelsPresent) { writePlanarBytesBody(planes, channelSizes, x, y, width, height); } else { writePlanarBytesBody(planes, channelSizes, x, y, width, height); } } qint32 KisTiledDataManager::numContiguousColumns(qint32 x, qint32 minY, qint32 maxY) const { qint32 numColumns; Q_UNUSED(minY); Q_UNUSED(maxY); if (x >= 0) { numColumns = KisTileData::WIDTH - (x % KisTileData::WIDTH); } else { numColumns = ((-x - 1) % KisTileData::WIDTH) + 1; } return numColumns; } qint32 KisTiledDataManager::numContiguousRows(qint32 y, qint32 minX, qint32 maxX) const { qint32 numRows; Q_UNUSED(minX); Q_UNUSED(maxX); if (y >= 0) { numRows = KisTileData::HEIGHT - (y % KisTileData::HEIGHT); } else { numRows = ((-y - 1) % KisTileData::HEIGHT) + 1; } return numRows; } qint32 KisTiledDataManager::rowStride(qint32 x, qint32 y) const { Q_UNUSED(x); Q_UNUSED(y); return KisTileData::WIDTH * pixelSize(); } void KisTiledDataManager::releaseInternalPools() { KisTileData::releaseInternalPools(); } diff --git a/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp b/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp index f2a008facc..4ca1813e5a 100644 --- a/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp +++ b/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp @@ -1,820 +1,947 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tiled_data_manager_test.h" #include #include "tiles3/kis_tiled_data_manager.h" #include "tiles_test_utils.h" #include "config-limit-long-tests.h" bool KisTiledDataManagerTest::checkHole(quint8* buffer, quint8 holeColor, QRect holeRect, quint8 backgroundColor, QRect backgroundRect) { for(qint32 y = backgroundRect.y(); y <= backgroundRect.bottom(); y++) { for(qint32 x = backgroundRect.x(); x <= backgroundRect.right(); x++) { quint8 expectedColor = holeRect.contains(x,y) ? holeColor : backgroundColor; if(*buffer != expectedColor) { qDebug() << "Expected" << expectedColor << "but found" << *buffer; return false; } buffer++; } } return true; } bool KisTiledDataManagerTest::checkTilesShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect) { for(qint32 row = tilesRect.y(); row <= tilesRect.bottom(); row++) { for(qint32 col = tilesRect.x(); col <= tilesRect.right(); col++) { KisTileSP srcTile = takeOldSrc ? srcDM->getOldTile(col, row) : srcDM->getTile(col, row, false); KisTileSP dstTile = takeOldDst ? dstDM->getOldTile(col, row) : dstDM->getTile(col, row, false); if(srcTile->tileData() != dstTile->tileData()) { qDebug() << "Expected tile data (" << col << row << ")" << srcTile->extent() << srcTile->tileData() << "but found" << dstTile->tileData(); qDebug() << "Expected" << srcTile->data()[0] << "but found" << dstTile->data()[0]; return false; } } } return true; } bool KisTiledDataManagerTest::checkTilesNotShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect) { for(qint32 row = tilesRect.y(); row <= tilesRect.bottom(); row++) { for(qint32 col = tilesRect.x(); col <= tilesRect.right(); col++) { KisTileSP srcTile = takeOldSrc ? srcDM->getOldTile(col, row) : srcDM->getTile(col, row, false); KisTileSP dstTile = takeOldDst ? dstDM->getOldTile(col, row) : dstDM->getTile(col, row, false); if(srcTile->tileData() == dstTile->tileData()) { qDebug() << "Expected tiles not be shared:"<< srcTile->extent(); return false; } } } return true; } void KisTiledDataManagerTest::testUndoingNewTiles() { // "growing extent bug" const QRect nullRect(qint32_MAX,qint32_MAX,0,0); quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTileSP emptyTile = srcDM.getTile(0, 0, false); QCOMPARE(srcDM.extent(), nullRect); KisMementoSP memento0 = srcDM.getMemento(); KisTileSP createdTile = srcDM.getTile(0, 0, true); srcDM.commit(); QCOMPARE(srcDM.extent(), QRect(0,0,64,64)); srcDM.rollback(memento0); QCOMPARE(srcDM.extent(), nullRect); } void KisTiledDataManagerTest::testPurgedAndEmptyTransactions() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); quint8 oddPixel1 = 128; QRect rect(0,0,512,512); QRect clearRect1(50,50,100,100); QRect clearRect2(150,50,100,100); quint8 *buffer = new quint8[rect.width()*rect.height()]; // purged transaction KisMementoSP memento0 = srcDM.getMemento(); srcDM.clear(clearRect1, &oddPixel1); srcDM.purgeHistory(memento0); memento0 = 0; srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1, defaultPixel, rect)); // one more purged transaction KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(clearRect2, &oddPixel1); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); srcDM.purgeHistory(memento1); memento1 = 0; srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); // empty one KisMementoSP memento2 = srcDM.getMemento(); srcDM.commit(); srcDM.rollback(memento2); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); // now check that everything works still KisMementoSP memento3 = srcDM.getMemento(); srcDM.setExtent(clearRect2); srcDM.commit(); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect2, defaultPixel, rect)); srcDM.rollback(memento3); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); } void KisTiledDataManagerTest::testUnversionedBitBlt() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); srcDM.clear(rect, &oddPixel1); dstDM.clear(rect, &oddPixel2); dstDM.bitBlt(&srcDM, cloneRect); quint8 *buffer = new quint8[rect.width()*rect.height()]; dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, oddPixel2, rect)); delete[] buffer; // Test whether tiles became shared QVERIFY(checkTilesShared(&srcDM, &dstDM, false, false, tilesRect)); } void KisTiledDataManagerTest::testVersionedBitBlt() { quint8 defaultPixel = 0; KisTiledDataManager srcDM1(1, &defaultPixel); KisTiledDataManager srcDM2(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; quint8 oddPixel4 = 131; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); KisMementoSP memento1 = srcDM1.getMemento(); srcDM1.clear(rect, &oddPixel1); srcDM2.clear(rect, &oddPixel2); dstDM.clear(rect, &oddPixel3); KisMementoSP memento2 = dstDM.getMemento(); dstDM.bitBlt(&srcDM1, cloneRect); QVERIFY(checkTilesShared(&srcDM1, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.commit(); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); KisMementoSP memento3 = srcDM2.getMemento(); srcDM2.clear(rect, &oddPixel4); KisMementoSP memento4 = dstDM.getMemento(); dstDM.bitBlt(&srcDM2, cloneRect); QVERIFY(checkTilesShared(&srcDM2, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM2, &srcDM2, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.commit(); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.rollback(memento4); QVERIFY(checkTilesShared(&srcDM1, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); dstDM.rollforward(memento4); QVERIFY(checkTilesShared(&srcDM2, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); } void KisTiledDataManagerTest::testBitBltOldData() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); quint8 *buffer = new quint8[rect.width()*rect.height()]; KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel1); srcDM.commit(); dstDM.bitBltOldData(&srcDM, cloneRect); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, defaultPixel, rect)); KisMementoSP memento2 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel2); dstDM.bitBltOldData(&srcDM, cloneRect); srcDM.commit(); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, defaultPixel, rect)); delete[] buffer; } void KisTiledDataManagerTest::testBitBltRough() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect actualCloneRect(64,64,320,320); QRect tilesRect(1,1,4,4); srcDM.clear(rect, &oddPixel1); dstDM.clear(rect, &oddPixel2); dstDM.bitBltRough(&srcDM, cloneRect); quint8 *buffer = new quint8[rect.width()*rect.height()]; dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, actualCloneRect, oddPixel2, rect)); // Test whether tiles became shared QVERIFY(checkTilesShared(&srcDM, &dstDM, false, false, tilesRect)); // check bitBltRoughOldData KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel3); dstDM.bitBltRoughOldData(&srcDM, cloneRect); srcDM.commit(); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, actualCloneRect, oddPixel2, rect)); delete[] buffer; } void KisTiledDataManagerTest::testTransactions() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; KisTileSP tile00; KisTileSP oldTile00; // Create a named transaction: versioning is enabled KisMementoSP memento1 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel1); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; // Create an anonymous transaction: versioning is disabled dm.commit(); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.clear(0, 0, 64, 64, &oddPixel2); // Versioning is disabled, i said! >:) tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; // And the last round: named transaction: KisMementoSP memento2 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel3); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel3, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; } void KisTiledDataManagerTest::testPurgeHistory() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; quint8 oddPixel4 = 131; KisMementoSP memento1 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel1); dm.commit(); KisMementoSP memento2 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel2); KisTileSP tile00; KisTileSP oldTile00; tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.purgeHistory(memento1); /** * Nothing nas changed in the visible state of the data manager */ tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.commit(); dm.purgeHistory(memento2); /** * We've removed all the history of the device, so it * became "unversioned". * NOTE: the return value for getOldTile() when there is no * history present is a subject for change */ tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; /** * Just test we won't crash when the memento is not * present in history anymore */ KisMementoSP memento3 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel3); dm.commit(); KisMementoSP memento4 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel4); dm.commit(); dm.rollback(memento4); dm.purgeHistory(memento3); dm.purgeHistory(memento4); } void KisTiledDataManagerTest::testUndoSetDefaultPixel() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect fillRect(0,0,64,64); KisTileSP tile00; KisTileSP tile10; tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(defaultPixel, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); KisMementoSP memento1 = dm.getMemento(); dm.clear(fillRect, &oddPixel1); dm.commit(); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); KisMementoSP memento2 = dm.getMemento(); dm.setDefaultPixel(&oddPixel2); dm.commit(); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, tile10->data(), TILESIZE)); dm.rollback(memento2); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollback(memento1); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(defaultPixel, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollforward(memento1); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollforward(memento2); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, tile10->data(), TILESIZE)); } //#include void KisTiledDataManagerTest::benchmarkReadOnlyTileLazy() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); /* * See KisTileHashTableTraits2 for more details */ const qint32 numTilesToTest = 0x7fff; //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK_ONCE { for(qint32 i = 0; i < numTilesToTest; i++) { KisTileSP tile = dm.getTile(i, i, false); } } //CALLGRIND_STOP_INSTRUMENTATION; } class KisSimpleClass : public KisShared { qint64 m_int; public: KisSimpleClass() { Q_UNUSED(m_int); } }; typedef KisSharedPtr KisSimpleClassSP; void KisTiledDataManagerTest::benchmarkSharedPointers() { const qint32 numIterations = 2 * 1000000; //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK_ONCE { for(qint32 i = 0; i < numIterations; i++) { KisSimpleClassSP pointer = new KisSimpleClass; pointer = 0; } } //CALLGRIND_STOP_INSTRUMENTATION; } void KisTiledDataManagerTest::benchmarkCOWImpl() { const int pixelSize = 8; quint8 defaultPixel[pixelSize]; memset(defaultPixel, 1, pixelSize); KisTiledDataManager dm(pixelSize, defaultPixel); KisMementoSP memento1 = dm.getMemento(); /** * Imagine a regular image of 4096x2048 pixels * (64x32 tiles) */ for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { KisTileSP tile = dm.getTile(j, i, true); tile->lockForWrite(); tile->unlock(); } } dm.commit(); QTest::qSleep(200); KisMementoSP memento2 = dm.getMemento(); QTest::qSleep(200); QBENCHMARK_ONCE { for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { KisTileSP tile = dm.getTile(j, i, true); tile->lockForWrite(); tile->unlock(); } } } dm.commit(); } void KisTiledDataManagerTest::benchmarkCOWNoPooler() { KisTileDataStore::instance()->testingSuspendPooler(); QTest::qSleep(200); benchmarkCOWImpl(); KisTileDataStore::instance()->testingResumePooler(); QTest::qSleep(200); } void KisTiledDataManagerTest::benchmarkCOWWithPooler() { benchmarkCOWImpl(); } /******************* Stress job ***********************/ #ifdef LIMIT_LONG_TESTS #define NUM_CYCLES 10000 #else #define NUM_CYCLES 100000 #endif #define NUM_TYPES 12 #define TILE_DIMENSION 64 /** * The data manager has partial guarantees of reentrancy. That is * you can call any arbitrary number of methods concurrently as long * as their access areas do not intersect. * * Though the rule can be quite tricky -- some of the methods always * use entire image as their access area, so they cannot be called * concurrently in any circumstances. * The examples are: clear(), commit(), rollback() and etc... */ #define run_exclusive(lock, _i) for(_i = 0, (lock).lockForWrite(); _i < 1; _i++, (lock).unlock()) #define run_concurrent(lock, _i) for(_i = 0, (lock).lockForRead(); _i < 1; _i++, (lock).unlock()) //#define run_exclusive(lock, _i) while(0) //#define run_concurrent(lock, _i) while(0) class KisStressJob : public QRunnable { public: KisStressJob(KisTiledDataManager &dataManager, QRect rect, QReadWriteLock &_lock) : m_accessRect(rect), dm(dataManager), lock(_lock) { } void run() override { qsrand(QTime::currentTime().msec()); for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = qrand() % NUM_TYPES; qint32 t; switch(type) { case 0: run_concurrent(lock,t) { quint8 *buf; buf = new quint8[dm.pixelSize()]; memcpy(buf, dm.defaultPixel(), dm.pixelSize()); dm.setDefaultPixel(buf); delete[] buf; } break; case 1: case 2: run_concurrent(lock,t) { KisTileSP tile; tile = dm.getTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION, false); tile->lockForRead(); tile->unlock(); tile = dm.getTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION, true); tile->lockForWrite(); tile->unlock(); tile = dm.getOldTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION); tile->lockForRead(); tile->unlock(); } break; case 3: run_concurrent(lock,t) { QRect newRect = dm.extent(); Q_UNUSED(newRect); } break; case 4: run_concurrent(lock,t) { dm.clear(m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height(), 4); } break; case 5: run_concurrent(lock,t) { quint8 *buf; buf = new quint8[m_accessRect.width() * m_accessRect.height() * dm.pixelSize()]; dm.readBytes(buf, m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height()); dm.writeBytes(buf, m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height()); delete[] buf; } break; case 6: run_concurrent(lock,t) { quint8 oddPixel = 13; KisTiledDataManager srcDM(1, &oddPixel); dm.bitBlt(&srcDM, m_accessRect); } break; case 7: case 8: run_exclusive(lock,t) { m_memento = dm.getMemento(); dm.clear(m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height(), 2); dm.commit(); dm.rollback(m_memento); dm.rollforward(m_memento); dm.purgeHistory(m_memento); m_memento = 0; } break; case 9: run_exclusive(lock,t) { bool b = dm.hasCurrentMemento(); Q_UNUSED(b); } break; case 10: run_exclusive(lock,t) { dm.clear(); } break; case 11: run_exclusive(lock,t) { dm.setExtent(m_accessRect); } break; } } } private: KisMementoSP m_memento; QRect m_accessRect; KisTiledDataManager &dm; QReadWriteLock &lock; }; void KisTiledDataManagerTest::stressTest() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); QReadWriteLock lock; +#ifdef LIMIT_LONG_TESTS + const int numThreads = 8; + const int numWorkers = 8; +#else + const int numThreads = 16; + const int numWorkers = 48; +#endif + QThreadPool pool; - pool.setMaxThreadCount(NUM_TYPES); + pool.setMaxThreadCount(numThreads); - QRect accessRect(0,0,100,100); - for(qint32 i = 0; i < NUM_TYPES; i++) { + QRect accessRect(0,0,512,512); + for(qint32 i = 0; i < numWorkers; i++) { KisStressJob *job = new KisStressJob(dm, accessRect, lock); pool.start(job); - accessRect.translate(100, 0); + accessRect.translate(512, 0); + } + pool.waitForDone(); +} + +template +void applyToRect(const QRect &rc, Func func) { + for (int y = rc.y(); y < rc.y() + rc.height(); y += KisTileData::HEIGHT) { + for (int x = rc.x(); x < rc.x() + rc.width(); x += KisTileData::WIDTH) { + const int col = x / KisTileData::WIDTH; + const int row = y / KisTileData::HEIGHT; + + func(col, row); + } + } +} + + +class LazyCopyingStressJob : public QRunnable +{ +public: + LazyCopyingStressJob(KisTiledDataManager &dataManager, + const QRect &rect, + QReadWriteLock &dmExclusiveLock, + QReadWriteLock &tileExclusiveLock, + int numCycles, + bool isWriter) + : m_accessRect(rect), + dm(dataManager), + m_dmExclusiveLock(dmExclusiveLock), + m_tileExclusiveLock(tileExclusiveLock), + m_numCycles(numCycles), + m_isWriter(isWriter) + { + } + + void run() override { + for(qint32 i = 0; i < m_numCycles; i++) { + + //const int epoch = i % 100; + int t; + + if (m_isWriter && 0) { + + } else { + const bool shouldClear = i % 5 <= 1; // 40% of requests are clears + const bool shouldWrite = i % 5 <= 3; // other 40% of requests are writes + + run_concurrent(m_dmExclusiveLock, t) { + if (shouldClear) { + QWriteLocker locker(&m_tileExclusiveLock); + dm.clear(m_accessRect, 4); + } else { + auto readFunc = [this] (int col, int row) { + KisTileSP tile = dm.getTile(col, row, false); + tile->lockForRead(); + tile->unlock(); + }; + + auto writeFunc = [this] (int col, int row) { + KisTileSP tile = dm.getTile(col, row, true); + tile->lockForWrite(); + tile->unlock(); + }; + + auto readOldFunc = [this] (int col, int row) { + KisTileSP tile = dm.getOldTile(col, row); + tile->lockForRead(); + tile->unlock(); + }; + + applyToRect(m_accessRect, readFunc); + if (shouldWrite) { + QReadLocker locker(&m_tileExclusiveLock); + applyToRect(m_accessRect, writeFunc); + } + applyToRect(m_accessRect, readOldFunc); + } + } + } + } + } + +private: + KisMementoSP m_memento; + QRect m_accessRect; + KisTiledDataManager &dm; + QReadWriteLock &m_dmExclusiveLock; + QReadWriteLock &m_tileExclusiveLock; + const int m_numCycles; + const bool m_isWriter; +}; + +void KisTiledDataManagerTest::stressTestLazyCopying() +{ + quint8 defaultPixel = 0; + KisTiledDataManager dm(1, &defaultPixel); + QReadWriteLock dmLock; + QReadWriteLock tileLock; + +#ifdef LIMIT_LONG_TESTS + const int numCycles = 10000; + const int numThreads = 8; + const int numWorkers = 8; +#else + const int numThreads = 16; + const int numWorkers = 32; + const int numCycles = 100000; +#endif + + QThreadPool pool; + pool.setMaxThreadCount(numThreads); + + const QRect accessRect(0,0,512,256); + for(qint32 i = 0; i < numWorkers; i++) { + const bool isWriter = i == 0; + LazyCopyingStressJob *job = new LazyCopyingStressJob(dm, accessRect, + dmLock, tileLock, + numCycles, isWriter); + pool.start(job); } pool.waitForDone(); } QTEST_MAIN(KisTiledDataManagerTest) diff --git a/libs/image/tiles3/tests/kis_tiled_data_manager_test.h b/libs/image/tiles3/tests/kis_tiled_data_manager_test.h index e37be08895..968b34263a 100644 --- a/libs/image/tiles3/tests/kis_tiled_data_manager_test.h +++ b/libs/image/tiles3/tests/kis_tiled_data_manager_test.h @@ -1,66 +1,68 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TILED_DATA_MANAGER_TEST_H #define KIS_TILED_DATA_MANAGER_TEST_H #include class KisTiledDataManager; class KisTiledDataManagerTest : public QObject { Q_OBJECT private: bool checkHole(quint8* buffer, quint8 holeColor, QRect holeRect, quint8 backgroundColor, QRect backgroundRect); bool checkTilesShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect); bool checkTilesNotShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect); void benchmarkCOWImpl(); private Q_SLOTS: void testUndoingNewTiles(); void testPurgedAndEmptyTransactions(); void testUnversionedBitBlt(); void testVersionedBitBlt(); void testBitBltOldData(); void testBitBltRough(); void testTransactions(); void testPurgeHistory(); void testUndoSetDefaultPixel(); void benchmarkReadOnlyTileLazy(); void benchmarkSharedPointers(); void benchmarkCOWNoPooler(); void benchmarkCOWWithPooler(); void stressTest(); + + void stressTestLazyCopying(); }; #endif /* KIS_TILED_DATA_MANAGER_TEST_H */ diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index ab635b6e6b..9cf6733548 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,857 +1,888 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" +#include +#include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType(ResourceType::Brushes, "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType(ResourceType::PaintOpPresets, "data", "/paintoppresets/"); KoResourcePaths::addResourceType(ResourceType::Workspaces, "data", "/workspaces/"); KoResourcePaths::addResourceType(ResourceType::WindowLayouts, "data", "/windowlayouts/"); KoResourcePaths::addResourceType(ResourceType::Sessions, "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType(ResourceType::Patterns, "data", "/patterns/", true); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/"); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/", true); KoResourcePaths::addResourceType(ResourceType::Palettes, "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType(ResourceType::FilterEffects, "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType(ResourceType::Symbols, "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType(ResourceType::GamutMasks, "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir(ResourceType::Gradients, "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Gradients, QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Patterns, "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Patterns, QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Brushes, "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Brushes, QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Palettes, "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir(ResourceType::Palettes, QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } bool KisApplication::registerResources() { KisResourceLoaderRegistry *reg = KisResourceLoaderRegistry::instance(); reg->add(new KisResourceLoader(ResourceType::PaintOpPresets, ResourceType::PaintOpPresets, i18n("Brush presets"), QStringList() << "application/x-krita-paintoppreset")); reg->add(new KisResourceLoader(ResourceSubType::GbrBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); reg->add(new KisResourceLoader(ResourceSubType::GihBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush-animated")); reg->add(new KisResourceLoader(ResourceSubType::SvgBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceSubType::PngBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/png")); reg->add(new KisResourceLoader(ResourceSubType::SegmentedGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-gimp-gradient")); reg->add(new KisResourceLoader(ResourceSubType::StopGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-karbon-gradient" << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::Palettes, ResourceType::Palettes, i18n("Palettes"), QStringList() << KisMimeDatabase::mimeTypeForSuffix("kpl") << KisMimeDatabase::mimeTypeForSuffix("gpl") << KisMimeDatabase::mimeTypeForSuffix("pal") << KisMimeDatabase::mimeTypeForSuffix("act") << KisMimeDatabase::mimeTypeForSuffix("aco") << KisMimeDatabase::mimeTypeForSuffix("css") << KisMimeDatabase::mimeTypeForSuffix("colors") << KisMimeDatabase::mimeTypeForSuffix("xml") << KisMimeDatabase::mimeTypeForSuffix("sbz"))); QList src = QImageReader::supportedMimeTypes(); QStringList allImageMimes; Q_FOREACH(const QByteArray ba, src) { allImageMimes << QString::fromUtf8(ba); } allImageMimes << KisMimeDatabase::mimeTypeForSuffix("pat"); reg->add(new KisResourceLoader(ResourceType::Patterns, ResourceType::Patterns, i18n("Patterns"), allImageMimes)); reg->add(new KisResourceLoader(ResourceType::Workspaces, ResourceType::Workspaces, i18n("Workspaces"), QStringList() << "application/x-krita-workspace")); reg->add(new KisResourceLoader(ResourceType::Symbols, ResourceType::Symbols, i18n("SVG symbol libraries"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::WindowLayouts, ResourceType::WindowLayouts, i18n("Window layouts"), QStringList() << "application/x-krita-windowlayout")); reg->add(new KisResourceLoader(ResourceType::Sessions, ResourceType::Sessions, i18n("Sessions"), QStringList() << "application/x-krita-session")); reg->add(new KisResourceLoader(ResourceType::GamutMasks, ResourceType::GamutMasks, i18n("Gamut masks"), QStringList() << "application/x-krita-gamutmask")); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), i18n("Could not create the resources cache database. Krita will quit now.")); return false; } KisResourceLocator::LocatorError r = KisResourceLocator::instance()->initialize(KoResourcePaths::getApplicationRoot() + "/share/krita"); connect(KisResourceLocator::instance(), SIGNAL(progressMessage(const QString&)), this, SLOT(setSplashScreenLoadingText(const QString&))); if (r != KisResourceLocator::LocatorError::Ok ) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), KisResourceLocator::instance()->errorMessages().join('\n') + i18n("\n\nKrita will quit now.")); return false; } return true; } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); + const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); - d->batchRun = (exportAs || !exportFileName.isEmpty()); - const bool needsMainWindow = !exportAs; + d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); + const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed - bool showmainWindow = !exportAs; // would be !batchRun; + bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources if (!registerResources()) { return false; } // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResourceSP windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); } + else if (exportSequence) { + KisDocument *doc = kisPart->createDocument(); + doc->setFileBatchMode(d->batchRun); + doc->openUrl(QUrl::fromLocalFile(fileName)); + qApp->processEvents(); // For vector layers to be updated + + if (!doc->image()->animationInterface()->hasAnimation()) { + errKrita << "This file has no animation." << endl; + QTimer::singleShot(0, this, SLOT(quit())); + return 1; + } + + doc->setFileBatchMode(true); + int sequenceStart = 0; + + KisAsyncAnimationFramesSaveDialog exporter(doc->image(), + doc->image()->animationInterface()->fullClipRange(), + exportFileName, + sequenceStart, + 0); + exporter.setBatchMode(d->batchRun); + KisAsyncAnimationFramesSaveDialog::Result result = + exporter.regenerateRange(0); + if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { + errKrita << i18n("Failed to render animation frames!") << endl; + } + QTimer::singleShot(0, this, SLOT(quit())); + } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(const QString &textToLoad) { if (d->splashScreen) { d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Visibile autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisApplicationArguments.cpp b/libs/ui/KisApplicationArguments.cpp index a2029e0f9e..2111f9a096 100644 --- a/libs/ui/KisApplicationArguments.cpp +++ b/libs/ui/KisApplicationArguments.cpp @@ -1,336 +1,344 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplicationArguments.h" #include #include #include #include #include #include #include #include #include #include #include #include struct Q_DECL_HIDDEN KisApplicationArguments::Private { Private() { } QStringList filenames; int dpiX {72}; int dpiY {72}; bool doTemplate {false}; bool exportAs {false}; + bool exportSequence {false}; QString exportFileName; QString workspace; QString windowLayout; QString session; bool canvasOnly {false}; bool noSplash {false}; bool fullScreen {false}; bool newImage {false}; QString colorModel {"RGBA"}; QString colorDepth {"U8"}; int width {2000}; int height {5000}; }; KisApplicationArguments::KisApplicationArguments() : d(new Private) { } KisApplicationArguments::~KisApplicationArguments() { } KisApplicationArguments::KisApplicationArguments(const QApplication &app) : d(new Private) { QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("template"), i18n("Open a new document with a template"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("new-image"), i18n("Create a new image on startup.\n" "Possible colorspace values are:\n" " * RGBA\n" " * XYZA\n" " * LABA\n" " * CMYKA\n" " * GRAY\n" " * YCbCrA\n" "Possible channel depth arguments are\n" " * U8 (8 bits integer)\n" " * U16 (16 bits integer)\n" " * F16 (16 bits floating point)\n" " * F32 (32 bits floating point)\n"), QLatin1String("colorspace,depth,width,height"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("workspace"), i18n("The name of the workspace to open Krita with"), QLatin1String("workspace"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("windowlayout"), i18n("The name of the window layout to open Krita with"), QLatin1String("windowlayout"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("load-session"), i18n("The name of the session to open Krita with"), QLatin1String("load-session"))); // NB: the argument "session" is already used by QGuiApplication parser.addOption(QCommandLineOption(QStringList() << QLatin1String("canvasonly"), i18n("Start Krita in canvas-only mode"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("nosplash"), i18n("Do not show the splash screen"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("fullscreen"), i18n("Start Krita in full-screen mode"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("dpi"), i18n("Override display DPI"), QLatin1String("dpiX,dpiY"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export"), i18n("Export to the given filename and exit"))); + parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-sequence"), i18n("Export animation to the given filename and exit"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-filename"), i18n("Filename for export"), QLatin1String("filename"))); parser.addPositionalArgument(QLatin1String("[file(s)]"), i18n("File(s) or URL(s) to open")); parser.process(app); QString dpiValues = parser.value("dpi"); if (!dpiValues.isEmpty()) { int sep = dpiValues.indexOf(QRegExp("[x, ]")); bool ok = true; if (sep != -1) { d->dpiY = dpiValues.mid(sep + 1).toInt(&ok); dpiValues.truncate(sep); } if (ok) { d->dpiX = dpiValues.toInt(&ok); if (ok) { if (!d->dpiY) d->dpiY = d->dpiX; } } } QString newImageValues = parser.value("new-image"); d->newImage = !newImageValues.isEmpty(); if (d->newImage) { QStringList v = newImageValues.split(","); if (v.size() != 4) { d->newImage = false; qWarning() << "Cannot create a new image: please specify colormodel, depth, width and height."; } d->colorModel = v[0].toUpper(); d->colorDepth = v[1].toUpper(); d->width = v[2].toInt(); d->height = v[3].toInt(); } d->exportFileName = parser.value("export-filename"); d->workspace = parser.value("workspace"); d->windowLayout = parser.value("windowlayout"); d->session = parser.value("load-session"); d->doTemplate = parser.isSet("template"); d->exportAs = parser.isSet("export"); + d->exportSequence = parser.isSet("export-sequence"); d->canvasOnly = parser.isSet("canvasonly"); d->noSplash = parser.isSet("nosplash"); d->fullScreen = parser.isSet("fullscreen"); const QDir currentDir = QDir::current(); Q_FOREACH (const QString &filename, parser.positionalArguments()) { d->filenames << currentDir.absoluteFilePath(filename); } } KisApplicationArguments::KisApplicationArguments(const KisApplicationArguments &rhs) : d(new Private) { d->filenames = rhs.filenames(); d->dpiX = rhs.dpiX(); d->dpiY = rhs.dpiY(); d->doTemplate = rhs.doTemplate(); d->exportAs = rhs.exportAs(); d->exportFileName = rhs.exportFileName(); d->canvasOnly = rhs.canvasOnly(); d->workspace = rhs.workspace(); d->windowLayout = rhs.windowLayout(); d->session = rhs.session(); d->noSplash = rhs.noSplash(); d->fullScreen = rhs.fullScreen(); } void KisApplicationArguments::operator=(const KisApplicationArguments &rhs) { d->filenames = rhs.filenames(); d->dpiX = rhs.dpiX(); d->dpiY = rhs.dpiY(); d->doTemplate = rhs.doTemplate(); d->exportAs = rhs.exportAs(); d->exportFileName = rhs.exportFileName(); d->canvasOnly = rhs.canvasOnly(); d->workspace = rhs.workspace(); d->windowLayout = rhs.windowLayout(); d->session = rhs.session(); d->noSplash = rhs.noSplash(); d->fullScreen = rhs.fullScreen(); } QByteArray KisApplicationArguments::serialize() { QByteArray ba; QBuffer buf(&ba); buf.open(QIODevice::WriteOnly); QDataStream ds(&buf); ds.setVersion(QDataStream::Qt_5_0); ds << d->filenames.count(); Q_FOREACH (const QString &filename, d->filenames) { ds << filename; } ds << d->dpiX; ds << d->dpiY; ds << d->doTemplate; ds << d->exportAs; ds << d->exportFileName; ds << d->workspace; ds << d->windowLayout; ds << d->session; ds << d->canvasOnly; ds << d->noSplash; ds << d->fullScreen; ds << d->newImage; ds << d->height; ds << d->width; ds << d->height; ds << d->colorModel; ds << d->colorDepth; buf.close(); return ba; } KisApplicationArguments KisApplicationArguments::deserialize(QByteArray &serialized) { KisApplicationArguments args; QBuffer buf(&serialized); buf.open(QIODevice::ReadOnly); QDataStream ds(&buf); ds.setVersion(QDataStream::Qt_5_0); int count; ds >> count; for(int i = 0; i < count; ++i) { QString s; ds >> s; args.d->filenames << s; } ds >> args.d->dpiX; ds >> args.d->dpiY; ds >> args.d->doTemplate; ds >> args.d->exportAs; ds >> args.d->exportFileName; ds >> args.d->workspace; ds >> args.d->windowLayout; ds >> args.d->session; ds >> args.d->canvasOnly; ds >> args.d->noSplash; ds >> args.d->fullScreen; ds >> args.d->newImage; ds >> args.d->height; ds >> args.d->width; ds >> args.d->height; ds >> args.d->colorModel; ds >> args.d->colorDepth; buf.close(); return args; } QStringList KisApplicationArguments::filenames() const { return d->filenames; } int KisApplicationArguments::dpiX() const { return d->dpiX; } int KisApplicationArguments::dpiY() const { return d->dpiY; } bool KisApplicationArguments::doTemplate() const { return d->doTemplate; } bool KisApplicationArguments::exportAs() const { return d->exportAs; } +bool KisApplicationArguments::exportSequence() const +{ + return d->exportSequence; +} + QString KisApplicationArguments::exportFileName() const { return d->exportFileName; } QString KisApplicationArguments::workspace() const { return d->workspace; } QString KisApplicationArguments::windowLayout() const { return d->windowLayout; } QString KisApplicationArguments::session() const { return d->session; } bool KisApplicationArguments::canvasOnly() const { return d->canvasOnly; } bool KisApplicationArguments::noSplash() const { return d->noSplash; } bool KisApplicationArguments::fullScreen() const { return d->fullScreen; } bool KisApplicationArguments::doNewImage() const { return d->newImage; } KisDocument *KisApplicationArguments::image() const { KisDocument *doc = KisPart::instance()->createDocument(); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(d->colorModel, d->colorDepth, ""); if (!cs) { qWarning() << "Could not create the colorspace for the new image. Check the colorspace and depth arguments."; return 0; } doc->newImage(i18n("Unnamed"), d->width, d->height, cs, KoColor(QColor(Qt::white), cs), KisConfig::CANVAS_COLOR, 1, "", 100.0); return doc; } diff --git a/libs/ui/KisApplicationArguments.h b/libs/ui/KisApplicationArguments.h index e7129d11ab..a3a358f8f3 100644 --- a/libs/ui/KisApplicationArguments.h +++ b/libs/ui/KisApplicationArguments.h @@ -1,67 +1,68 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISAPPLICATIONARGUMENTS_H #define KISAPPLICATIONARGUMENTS_H #include class QApplication; class QByteArray; class QStringList; class KisDocument; #include "kritaui_export.h" class KRITAUI_EXPORT KisApplicationArguments { public: KisApplicationArguments(const QApplication &app); KisApplicationArguments(const KisApplicationArguments &rhs); ~KisApplicationArguments(); void operator=(const KisApplicationArguments& rhs); QByteArray serialize(); static KisApplicationArguments deserialize(QByteArray &serialized); QStringList filenames() const; int dpiX() const; int dpiY() const; bool doTemplate() const; bool exportAs() const; + bool exportSequence() const; QString exportFileName() const; QString workspace() const; QString windowLayout() const; QString session() const; bool canvasOnly() const; bool noSplash() const; bool fullScreen() const; bool doNewImage() const; KisDocument *image() const; private: KisApplicationArguments(); struct Private; const QScopedPointer d; }; #endif // KISAPPLICATIONARGUMENTS_H diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index df1e8efc1b..b7ce6bda68 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1962 +1,1962 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , mirrorAxisConfig(rhs.mirrorAxisConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) , paletteList(rhs.paletteList) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; QList paletteList; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { KBackup::simpleBackupFile(job.filePath, backupDir, suffix); } else if (numOfBackupsKept > 2) { KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept); } } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) .arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK")); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (ret) { - if ((flags & DontAddToRecent)) { + if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { if (window && window->viewManager()) { updater->cancel(); } QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList) { d->paletteList = paletteList; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8" , name , width, height , imageResolution * 72.0 , image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name() , image->colorSpace()->profile()->name() , numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 8c564d9dcf..a99d154048 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,415 +1,417 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPatternSP currentPattern; KoAbstractGradientSP currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-running actions * will have correct brush parameters. Theoretically this cloning * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ KisPaintOpPresetSP p = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (p) { m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->clone(); } #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); + painter->setStrokeStyle(m_d->strokeStyle); + /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset ? m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->selectionOverride) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return m_d->currentPaintOpPreset->settings()->isAirbrushing(); } qreal KisResourcesSnapshot::airbrushingInterval() const { return m_d->currentPaintOpPreset->settings()->airbrushInterval(); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return m_d->currentPaintOpPreset->settings()->useSpacingUpdates(); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPatternSP KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush; } diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh new file mode 100755 index 0000000000..0829b75f0f --- /dev/null +++ b/packaging/macos/osxbuild.sh @@ -0,0 +1,403 @@ +#!/usr/bin/env sh + +# Stop at any error +# For debug purposes +# set -e +# set -x + +# osxbuild.sh automates building and installing of krita and krita dependencies +# for OSX, the script only needs you to set BUILDROOT environment to work +# properly. +# + +# Run with no args for a short help about each command. + +# builddeps: Attempts to build krita dependencies in the necessary order, +# intermediate steps for creating symlinks and fixing rpath of some +# packages midway is also managed. Order goes from top to bottom, to add +# new steps just place them in the proper place. + +# rebuilddeps: This re-runs all make and make install of dependencies 3rdparty +# this was needed as deleting the entire install directory an rerunning build +# step for dependencies does not install if they are already built. This step +# forces installation. Have not tested it lately so it might not be needed anymore + +# build: Runs cmake build and make step for krita sources. It always run cmake step, so +# it might take a bit longer than a pure on the source tree. The script tries +# to set the make flag -jN to a proper N. + +# install: Runs install step for krita sources. + +# fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by +# default it fails to set a proper @rpath + +# buildinstall: Runs build, install and fixboost steps. + + +if test -z $BUILDROOT; then + echo "ERROR: BUILDROOT env not set, exiting!" + echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" + exit +fi + +echo "BUILDROOT set to ${BUILDROOT}" +export KIS_SRC_DIR=${BUILDROOT}/krita +export KIS_TBUILD_DIR=${BUILDROOT}/depbuild +export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall +export KIS_DOWN_DIR=${BUILDROOT}/down +export KIS_BUILD_DIR=${BUILDROOT}/kisbuild +export KIS_INSTALL_DIR=${BUILDROOT}/i + +# flags for OSX environment +# We only support from 10.11 up +export MACOSX_DEPLOYMENT_TARGET=10.11 +export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 + +# Build time variables +if test -z $(which cmake); then + echo "ERROR: cmake not found, exiting!" + exit +fi + +export PATH=${KIS_INSTALL_DIR}/bin:$PATH +export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:${C_INCLUDE_PATH} +export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:${CPLUS_INCLUDE_PATH} +export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:${LIBRARY_PATH} +# export CPPFLAGS=-I${KIS_INSTALL_DIR}/include +# export LDFLAGS=-L${KIS_INSTALL_DIR}/lib + +export PYTHONHOME=${KIS_INSTALL_DIR} +# export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5 + +# This will make the debug output prettier +export KDE_COLOR_DEBUG=1 +export QTEST_COLORED=1 + + +# configure max core for make compile +((MAKE_THREADS=1)) +if test ${OSTYPE} == "darwin*"; then + ((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1)) +fi + +check_dir_path () { + echo "Checking if ${1} exists and is dir… \r" + if test -d ${1}; then + echo "OK" + return 0 + elif test -e ${1}; then + echo "ERROR: file ${1} exists but is not a directory!" + return 1 + else + echo "Creating ${1}" + mkdir ${1} + fi + return 0 +} +# builds dependencies for the first time +build_3rdparty () { + echo "building in ${KIS_TBUILD_DIR}" + + check_dir_path ${KIS_TBUILD_DIR} + check_dir_path ${KIS_DOWN_DIR} + check_dir_path ${KIS_INSTALL_DIR} + + cd ${KIS_TBUILD_DIR} + + cmake ${KIS_SRC_DIR}/3rdparty/ \ + -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ + -DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \ + -DINSTALL_ROOT=${KIS_INSTALL_DIR} + + # -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \ + # -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib + + echo "finished 3rdparty build setup" + # make preinstall + echo "finished make step" + + cmake_3rdparty () { + for package in ${@:1:${#@}}; do + echo "Building ${package}" + cmake --build . --config RelWithDebInfo --target ${package} + done + } + + if ! test -z ${1}; then + echo "Starting build of ${1} $(test -n ${1})" + cmake_3rdparty ${1} + exit + fi + + # build 3rdparty tools + # The order must not be changed! + cmake_3rdparty \ + ext_gettext \ + ext_openssl \ + ext_qt \ + ext_zlib \ + ext_boost \ + ext_eigen3 \ + ext_exiv2 \ + ext_fftw3 \ + ext_ilmbase \ + ext_jpeg \ + ext_lcms2 \ + ext_ocio + + # open exr will fail the first time is called + # rpath needs to be fixed an build rerun + if cmake_3rdparty ext_openexr; then + echo "open_exr built success!" + else + if install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable; then + echo "Added rpath for b44ExpLogTable" + fi + if install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups; then + echo "Added rpath for dwaLookups" + fi + echo "Rerunning ext_openexr build" + cmake_3rdparty ext_openexr + fi + + cmake_3rdparty \ + ext_png \ + ext_tiff \ + ext_gsl \ + ext_vc \ + ext_libraw \ + ext_giflib + + # for python + # for sip PYQT_SIP_DIR_OVERRIDE=/usr/local/share/sip/qt5 + cmake_3rdparty \ + ext_python \ + ext_sip \ + ext_pyqt + + # on osx qmake is not correctly set to qmake-qt5 for some reason + ln -s -f ${KIS_INSTALL_DIR}/bin/qmake ${KIS_INSTALL_DIR}/bin/qmake-qt5 + + cmake_3rdparty \ + ext_frameworks \ + ext_extra_cmake_modules \ + ext_karchive \ + ext_kconfig \ + ext_kwidgetsaddons \ + ext_kcompletion \ + ext_kcoreaddons \ + ext_kguiaddons \ + ext_ki18n \ + ext_kitemmodels \ + ext_kitemviews \ + ext_kimageformats \ + ext_kwindowsystem \ + ext_quazip +} + +# Recall cmake for all 3rd party packages +# make is only on target first run +# subsequent runs only call make install +rebuild_3rdparty () { + echo "starting rebuild of 3rdparty packages" + build_install_ext() { + for pkg in ${@:1:${#@}}; do + { + cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-build + } || { + cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg} + } + echo "Installing ${pkg} files..." + if test ${pkg} = "ext_boost"; then + echo "Copying boost files..." + cp bin.v2/libs/system/build/darwin-4.2.1/release/threading-multi/libboost_system.dylib ${KIS_INSTALL_DIR}/lib/ + cp bin.v2/libs/system/build/darwin-4.2.1/release/link-static/threading-multi/libboost_system.a ${KIS_INSTALL_DIR}/lib/ + # copy boost into i/include :: + cp -r boost ${KIS_INSTALL_DIR}/include + else + make -j${MAKE_THREADS} + make install + fi + cd ${KIS_TBUILD_DIR} + done + } + + build_install_frameworks() { + for pkg in \ + ext_extra_cmake_modules \ + ext_karchive \ + ext_kconfig \ + ext_kwidgetsaddons \ + ext_kcompletion \ + ext_kcoreaddons \ + ext_kguiaddons \ + ext_ki18n \ + ext_kitemmodels \ + ext_kitemviews \ + ext_kimageformats \ + ext_kwindowsystem \ + ; do + cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-build + echo "Installing ${pkg} files..." + make install ${pkg} + done + } + + if ! test -n ${1}; then + build_install_ext ${1} + exit + fi + + build_install_ext \ + ext_gettext \ + ext_openssl \ + + # qt builds inside qt_ext, make install must be run inside. + build_install_ext ext_qt + build_install_ext ext_zlib + + # ext_boost + build_install_ext ext_boost + + build_install_ext \ + ext_eigen3 \ + ext_exiv2 \ + ext_fftw3 \ + ext_ilmbase \ + ext_jpeg \ + ext_lcms2 \ + ext_ocio \ + ext_openexr \ + ext_png \ + ext_tiff \ + ext_gsl \ + ext_vc \ + ext_libraw \ + ext_giflib + + # for python + build_install_ext ext_python + build_install_ext ext_sip + build_install_ext ext_pyqt + + # force creation of symlink, useful if install dir was wiped out. + ln -s -f ${KIS_INSTALL_DIR}/bin/qmake ${KIS_INSTALL_DIR}/bin/qmake-qt5 + + build_install_frameworks + +} + +#not tested +set_krita_dirs() { + if ! test -z ${1}; then + KIS_BUILD_DIR=${BUILDROOT}/b_${1} + KIS_INSTALL_DIR=${BUILDROOT}/i_${1} + KIS_SRC_DIR=${BUILDROOT}/src_${1} + fi +} +# build_krita +# run cmake krita +build_krita () { + set_krita_dirs ${1} + echo ${KIS_BUILD_DIR} + echo ${KIS_INSTALL_DIR} + check_dir_path ${KIS_BUILD_DIR} + cd ${KIS_BUILD_DIR} + + cmake ${KIS_SRC_DIR} \ + -DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \ + -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ + -DDEFINE_NO_DEPRECATED=1 \ + -DBUILD_TESTING=OFF \ + -DHIDE_SAFE_ASSERTS=ON \ + -DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \ + -DPYQT_SIP_DIR_OVERRIDE=$KIS_INSTALL_DIR/share/sip/ \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 + + # copiling phase + make -j${MAKE_THREADS} + + # compile integrations + if test ${OSTYPE} == "darwin*"; then + cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook + make -j${MAKE_THREADS} + fi +} + +install_krita () { + set_krita_dirs ${1} + check_dir_path ${KIS_BUILD_DIR} + cd ${KIS_BUILD_DIR} + make install + + # compile integrations + if test ${OSTYPE} == "darwin*"; then + cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook + make install + fi +} + +fix_boost_rpath () { + set_krita_dirs ${1} + # install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt + if $(install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita); then + echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin" + fi + # install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita + install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita + + FILES=$(find -L ${KIS_INSTALL_DIR} -name '*so' -o -name '*dylib') + for FILE in $FILES; do + if test -n "$(otool -L $FILE | grep boost)"; then + echo "Fixing… $FILE" + install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE + fi + done +} + +print_usage () { + echo "USAGE: osxbuild.sh [pkg]" + echo "BUILDSTEPS:\t\t" + echo "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg" + echo "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg + \t\t\t usefull for cleaning install directory and quickly reinstall all deps." + echo "\n fixboost \t\t Fixes broken boost \@rpath on OSX" + echo "\n build \t\t\t Builds krita" + echo "\n install \t\t Installs krita" + echo "\n buildinstall \t\t Build and Installs krita, running fixboost after installing" + echo "" +} + +if test ${#} -eq 0; then + echo "ERROR: No option given!" + print_usage + exit 1 +fi + +if test ${1} = "builddeps"; then + build_3rdparty ${2} + +elif test ${1} = "rebuilddeps"; then + rebuild_3rdparty ${2} + +elif test ${1} = "fixboost"; then + fix_boost_rpath + +elif test ${1} = "build"; then + build_krita ${2} + +elif test ${1} = "install"; then + install_krita ${2} + +elif test ${1} = "buildinstall"; then + build_krita ${2} + install_krita ${2} + fix_boost_rpath ${2} + +else + echo "Option ${1} not supported" + print_usage +fi + +# after finishig sometimes it complains about missing matching quotes. \ No newline at end of file diff --git a/packaging/macos/osxdeploy.sh b/packaging/macos/osxdeploy.sh new file mode 100755 index 0000000000..6f6f0d9b3a --- /dev/null +++ b/packaging/macos/osxdeploy.sh @@ -0,0 +1,348 @@ +#!/usr/bin/env sh + +# Krita tool to create dmg from installed source +# Copies all files to a folder to be converted into the final dmg +# Background image must be set for it to deploy correcly + +# osxdeploy.sh automates the creation of the release DMG. It needs an image +# either png or jpg as first argument as it will use the image to set +# the background of the DMG. + +# Necessary files can be downloaded from https://drive.google.com/drive/folders/15cUhCou7ya9ktjfhbzRaL7_IpntzxG4j?usp=sharing + +# A short explanation of what it does: + +# - Creates a copy of "krita-template" folder (this containes Terms of use +# and Applications) into kritadmg folder. +# Application folder symlink can be created with applescript but Terms of use contents cannot, +# also working like this allows to add other files to dmg if needed. +# Place the folder in ${BUILDROOT} + +# - Copies krita.app contents to kritadmg folder +# - Copies i/share to Contents/Resources excluding unnecesary files +# - Copies translations, qml and quicklook PlugIns +# - Copies i/plugins and i/lib/plugins to Contents/PlugIns + +# - Runs macdeployqt: macdeployqt is not built by default in ext_qt +# build by: +# cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src +# make sub-macdeployqt-all +# make sub-macdeployqt-install_subtargets +# make install + +# the script changes dir to installation/bin to run macdeployqt as it can be buggy +# if not runned from the same folder as the binary is on. + +# - Fix rpath from krita bin +# - Find missing libraries from plugins and copy to Framworks or plugins. +# This uses oTool iterative to find all unique libraries, then it searches each +# library fond in folder, and if not found attempts to copy contents +# to the appropiate folder, either Frameworks (if frameworks is in namefile, or +# library has plugin isnot in path), or plugin if otherwise. + +# - Builds DMG +# Building DMG creates a new dmg with the contents of +# mounts the dmg and sets the style for dmg. +# unmount +# Compress resulting dmg into krita_nightly-.dmg +# deletes temporary files. + + +DMG_title="krita" #if changed krita.temp.dmg must be deleted manually + +if test -z ${BUILDROOT}; then + echo "ERROR: BUILDROOT env not set!" + echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" + echo "exiting..." + exit +fi + +# There is some duplication between build and deploy scripts +# a config env file could would be a nice idea. +KIS_INSTALL_DIR=${BUILDROOT}/i +KIS_BUILD_DIR=${BUILDROOT}/kisbuild # only used for getting git sha number +KRITA_DMG=${BUILDROOT}/kritadmg +KRITA_DMG_TEMPLATE=${BUILDROOT}/kritadmg-template + +# flags for OSX environment +# We only support from 10.11 up +export MACOSX_DEPLOYMENT_TARGET=10.11 +export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 + +print_usage () { + echo "USAGE: osxdeploy.sh [pkg]" + echo "\t osxdeploy needs an input image to add to the dmg background + \t image recomended size is at least 950x500\n" +} + +if test ${#} -eq 0; then + echo "ERROR: no option given" + print_usage + exit 1 +fi + +if [[ -f ${1} ]]; then + DMG_validBG=0 + echo "attempting to check background is valid jpg or png..." + BG_FORMAT=$(sips --getProperty format ${1} | awk '{printf $2}') + + if [[ "png" = ${BG_FORMAT} || "jpeg" = ${BG_FORMAT} ]];then + echo "valid image file" + DMG_background=${1} + DMG_validBG=1 + fi +fi + +if [[ ${DMG_validBG} -eq 0 ]]; then + echo "No jpg or png valid file detected!!" + echo "exiting" + exit +fi + +countArgs () { + echo "${#}" +} + +stringContains () { + echo "$(grep "${2}" <<< "${1}")" + +} + +find_needed_libs () { + local libs_used="" + for soFile in $(find ${KRITA_DMG}/krita.app/Contents/PlugIns -name "*.so" -or -name "*.dylib"); do + soFy=$(otool -L ${soFile} | cut -d " " -f 1) + echo "collecting libs from ${soFile}…" >&2 + for lib in ${soFy}; do + if test "${lib:0:1}" = "@"; then + if test -z "$(grep ${lib##*/} <<< ${libs_used})" ; then + local libs_used="${libs_used} ${lib##*/} " + fi + fi + done + done + echo ${libs_used} +} + +find_missing_libs (){ + echo "Searching for missing libs on deployment folders…" >&2 + local libs_missing="" + for lib in ${@}; do + if test -z "$(find ${BUILDROOT}/kritadmg/krita.app/Contents/ -name ${lib})"; then + echo "Adding ${lib} to missing libraries." >&2 + libs_missing="${libs_missing} ${lib}" + fi + done + echo ${libs_missing} +} + +copy_missing_libs () { + for lib in ${@}; do + result=$(find "${BUILDROOT}/i" -name "${lib}") + + if test $(countArgs ${result}) -eq 1; then + echo ${result} + if [ "$(stringContains "${result}" "plugin")" ]; then + echo "copying ${lib} to plugins dir" + cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/PlugIns/ + else + echo "copying ${lib} to Frameworks dir" + cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/Frameworks/ + fi + else + echo "${lib} might be a missing framework" + if [ "$(stringContains "${result}" "framework")" ]; then + echo "copying framework ${BUILDROOT}/i/lib/${lib}.framework to dmg" + cp -pvr ${BUILDROOT}/i/lib/${lib}.framework ${KRITA_DMG}/krita.app/Contents/Frameworks/ + fi + fi + done +} + +krita_findmissinglibs() { + echo "Adding missin glibraries for plugins" + neededLibs=$(find_needed_libs) + missingLibs=$(find_missing_libs ${neededLibs}) + + if test $(countArgs ${missingLibs}) -gt 0; then + echo "Found missing libs!" + echo "${missingLibs}\n" + copy_missing_libs ${missingLibs} + else + echo "No missing libraries found." + fi + + echo "Done!" +} + + +krita_deploy () { + # fix_boost_rpath + + cd ${BUILDROOT} + # Update files in krita.app + rm -rf ./krita.dmg ${KRITA_DMG} + # Copy new builtFiles + echo "Copying krita.app..." + rsync -riul ${KRITA_DMG_TEMPLATE}/ ${KRITA_DMG} + rsync -priul ${KIS_INSTALL_DIR}/bin/krita.app ${KRITA_DMG} + + echo "Copying share..." + # Deletes old copies of translation and qml to be recreated + cd ${KIS_INSTALL_DIR}/share/ + rsync -priul --delete --delete-excluded ./ \ + --exclude aclocal \ + --exclude doc \ + --exclude ECM \ + --exclude eigen3 \ + --exclude emacs \ + --exclude gettext \ + --exclude gettext-0.19.8 \ + --exclude info \ + --exclude kf5 \ + --exclude kservices5 \ + --exclude man \ + --exclude ocio \ + --exclude pkgconfig \ + --exclude mime \ + --exclude translations \ + --exclude qml \ + ${KRITA_DMG}/krita.app/Contents/Resources + + cd ${BUILDROOT} + + echo "Copying translations..." + rsync -priul ${KIS_INSTALL_DIR}/translations/ \ + ${KRITA_DMG}/krita.app/Contents/Resources/translations + + echo "Copying kritaquicklook..." + mkdir -p ${KRITA_DMG}/krita.app/Contents/Library/QuickLook + rsync -priul ${KIS_INSTALL_DIR}/plugins/kritaquicklook.qlgenerator ${KRITA_DMG}/krita.app/Contents/Library/QuickLook + + cd ${KRITA_DMG}/krita.app/Contents + ln -shF Resources share + + echo "Copying qml..." + rsync -priul ${KIS_INSTALL_DIR}/qml Resources/qml + + echo "Copying plugins..." + mkdir -p ${KRITA_DMG}/krita.app/Contents/PlugIns + # exclude kritaquicklook.qlgenerator/ + cd ${KIS_INSTALL_DIR}/plugins/ + rsync -priul --delete --delete-excluded ./ \ + --exclude kritaquicklook.qlgenerator \ + ${KRITA_DMG}/krita.app/Contents/PlugIns + + cd ${BUILDROOT} + rsync -priul ${KIS_INSTALL_DIR}/lib/kritaplugins/ ${KRITA_DMG}/krita.app/Contents/PlugIns + + # rsync -prul /Volumes/Osiris/programs/krita-master/i/lib/libkrita* Frameworks/ + + # activate for python enabled Krita + # echo "Copying python..." + # cp -r ${KIS_INSTALL_DIR}/lib/python3.5 Frameworks/ + # cp -r ${KIS_INSTALL_DIR}/lib/krita-python-libs Frameworks/ + + # XXX: fix rpath for krita.so + # echo "Copying sip..." + # rsync -Prvul ${KIS_INSTALL_DIR}/sip Frameworks/ + + # To avoid errors macdeployqt must be run from bin location + # ext_qt will not build macdeployqt by default so it must be build manually + # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src + # make sub-macdeployqt-all + # make sub-macdeployqt-install_subtargets + # make install + echo "Preparing ${KRITA_DMG} for deployment..." + echo "Running macdeployqt..." + cd ${KIS_INSTALL_DIR}/bin + ./macdeployqt ${KRITA_DMG}/krita.app \ + -verbose=0 \ + -executable=${KRITA_DMG}/krita.app/Contents/MacOS/krita \ + -libpath=${KIS_INSTALL_DIR}/lib \ + -qmldir=${KIS_INSTALL_DIR}/qml \ + # -extra-plugins=${KIS_INSTALL_DIR}/lib/kritaplugins \ + # -extra-plugins=${KIS_INSTALL_DIR}/lib/plugins \ + # -extra-plugins=${KIS_INSTALL_DIR}/plugins + + cd ${BUILDROOT} + echo "macdeployqt done!" + install_name_tool -delete_rpath @loader_path/../../../../lib ${KRITA_DMG}/krita.app/Contents/MacOS/krita + rm -rf ${KRITA_DMG}/krita.app/Contents/PlugIns/kf5/org.kde.kwindowsystem.platforms + + # repair krita for plugins + krita_findmissinglibs +} + +createDMG () { + echo "Starting creation of dmg..." + cd ${BUILDROOT} + DMG_size=500 + + ## Build dmg from folder + + # create dmg on local system + # usage of -fsargs minimze gaps at front of filesystem (reduce size) + hdiutil create -srcfolder "${KRITA_DMG}" -volname "${DMG_title}" -fs HFS+ \ + -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DMG_size}m krita.temp.dmg + # Next line is only useful if we have a dmg as a template! + # previous hdiutil must be uncommented + # cp krita-template.dmg krita.dmg + + device=$(hdiutil attach -readwrite -noverify -noautoopen "krita.temp.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}') + + # rsync -priul --delete ${KRITA_DMG}/krita.app "/Volumes/${DMG_title}" + + # Set style for dmg + if [[ ! -d "/Volumes/${DMG_title}/.background" ]]; then + mkdir "/Volumes/${DMG_title}/.background" + fi + cp ${BUILDROOT}/${DMG_background} "/Volumes/${DMG_title}/.background/" + + ## Apple script to set style + echo ' + tell application "Finder" + tell disk "'${DMG_title}'" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {186, 156, 956, 592} + set theViewOptions to the icon view options of container window + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 80 + set background picture of theViewOptions to file ".background:'${DMG_background}'" + set position of item "'krita.app'" of container window to {279, 272} + set position of item "Applications" of container window to {597, 272} + set position of item "Terms of Use" of container window to {597, 110} + update without registering applications + delay 1 + close + end tell + end tell + ' | osascript + + + chmod -Rf go-w "/Volumes/${DMG_title}" + + # ensure all writting operations to dmg are over + sync + + hdiutil detach $device + hdiutil convert "krita.temp.dmg" -format UDZO -imagekey -zlib-level=9 -o krita-out.dmg + + # Add git version number + GIT_SHA=$(grep "#define KRITA_GIT_SHA1_STRING" ${KIS_BUILD_DIR}/libs/version/kritagitversion.h | awk '{gsub(/"/, "", $3); printf $3}') + + mv krita-out.dmg krita-nightly_${GIT_SHA}.dmg + echo "moved krita-out.dmg to krita-nightly_${GIT_SHA}.dmg" + rm krita.temp.dmg + + echo "dmg done!" +} + +# Run deploy command, instalation is assumed to exist in BUILDROOT/i +# krita_deploy + +# Create DMG from files insiede ${KRITA_DMG} folder +createDMG diff --git a/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp b/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp index 89ee490180..9706e40157 100644 --- a/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp +++ b/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp @@ -1,274 +1,281 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisGLImageWidget.h" #include #include #include #include "kis_debug.h" #include +#include #include "KisGLImageF16.h" namespace { inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } } KisGLImageWidget::KisGLImageWidget(QWidget *parent) : KisGLImageWidget(QSurfaceFormat::sRGBColorSpace, parent) { } KisGLImageWidget::KisGLImageWidget(QSurfaceFormat::ColorSpace colorSpace, QWidget *parent) : QOpenGLWidget(parent), m_texture(QOpenGLTexture::Target2D) { setTextureFormat(GL_RGBA16F); #ifdef HAVE_HDR setTextureColorSpace(colorSpace); #endif setUpdateBehavior(QOpenGLWidget::NoPartialUpdate); } +KisGLImageWidget::~KisGLImageWidget() +{ + // force releasing the reourses on destruction + slotOpenGLContextDestroyed(); +} + void KisGLImageWidget::initializeGL() { initializeOpenGLFunctions(); connect(context(), SIGNAL(aboutToBeDestroyed()), SLOT(slotOpenGLContextDestroyed())); m_shader.reset(new QOpenGLShaderProgram); QFile vertexShaderFile(QString(":/") + "kis_gl_image_widget.vert"); vertexShaderFile.open(QIODevice::ReadOnly); QString vertSource = vertexShaderFile.readAll(); QFile fragShaderFile(QString(":/") + "kis_gl_image_widget.frag"); fragShaderFile.open(QIODevice::ReadOnly); QString fragSource = fragShaderFile.readAll(); if (context()->isOpenGLES()) { const char *versionHelper = "#define USE_OPENGLES\n"; vertSource.prepend(versionHelper); fragSource.prepend(versionHelper); const char *versionDefinition = "#version 100\n"; vertSource.prepend(versionDefinition); fragSource.prepend(versionDefinition); } else { - const char *versionDefinition = "#version 330 core\n"; + const char *versionDefinition = KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n"; vertSource.prepend(versionDefinition); fragSource.prepend(versionDefinition); } if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource)) { qDebug() << "Could not add vertex code"; return; } if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource)) { qDebug() << "Could not add fragment code"; return; } if (!m_shader->link()) { qDebug() << "Could not link"; return; } if (!m_shader->bind()) { qDebug() << "Could not bind"; return; } m_shader->release(); m_vao.create(); m_vao.bind(); m_verticesBuffer.create(); updateVerticesBuffer(this->rect()); QVector textureVertices(6); rectToTexCoords(textureVertices.data(), QRect(0.0, 0.0, 1.0, 1.0)); m_textureVerticesBuffer.create(); m_textureVerticesBuffer.bind(); m_textureVerticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); m_textureVerticesBuffer.allocate(2 * 3 * sizeof(QVector2D)); m_verticesBuffer.write(0, textureVertices.data(), m_textureVerticesBuffer.size()); m_textureVerticesBuffer.release(); m_vao.release(); if (!m_sourceImage.isNull()) { loadImage(m_sourceImage); } } void KisGLImageWidget::slotOpenGLContextDestroyed() { this->makeCurrent(); m_shader.reset(); m_texture.destroy(); m_verticesBuffer.destroy(); m_textureVerticesBuffer.destroy(); m_vao.destroy(); m_havePendingTextureUpdate = false; this->doneCurrent(); } void KisGLImageWidget::updateVerticesBuffer(const QRect &rect) { if (!m_vao.isCreated() || !m_verticesBuffer.isCreated()) return; QVector vertices(6); rectToVertices(vertices.data(), rect); m_verticesBuffer.bind(); m_verticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); m_verticesBuffer.allocate(2 * 3 * sizeof(QVector3D)); m_verticesBuffer.write(0, vertices.data(), m_verticesBuffer.size()); m_verticesBuffer.release(); } void KisGLImageWidget::paintGL() { const QColor bgColor = palette().background().color(); // TODO: fix conversion to the destination surface space //glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f); glClearColor(0.3, 0.2, 0.8, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (m_havePendingTextureUpdate) { m_havePendingTextureUpdate = false; if (!m_texture.isCreated() || m_sourceImage.width() != m_texture.width() || m_sourceImage.height() != m_texture.height()) { if (m_texture.isCreated()) { m_texture.destroy(); } m_texture.setFormat(QOpenGLTexture::RGBA16F); m_texture.setSize(m_sourceImage.width(), m_sourceImage.height()); m_texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::Float16); m_texture.setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); m_texture.setMagnificationFilter(QOpenGLTexture::Linear); m_texture.setWrapMode(QOpenGLTexture::ClampToEdge); } m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::Float16, m_sourceImage.constData()); } if (!m_texture.isCreated()) return; m_vao.bind(); m_shader->bind(); { QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, -1, 1); QMatrix4x4 viewProjectionMatrix; // use a QTransform to scale, translate, rotate your view QTransform transform; // TODO: noop! viewProjectionMatrix = projectionMatrix * QMatrix4x4(transform); m_shader->setUniformValue("viewProjectionMatrix", viewProjectionMatrix); } m_shader->enableAttributeArray("vertexPosition"); m_verticesBuffer.bind(); m_shader->setAttributeBuffer("vertexPosition", GL_FLOAT, 0, 3); m_shader->enableAttributeArray("texturePosition"); m_textureVerticesBuffer.bind(); m_shader->setAttributeBuffer("texturePosition", GL_FLOAT, 0, 2); glActiveTexture(GL_TEXTURE0); m_texture.bind(); // draw 2 triangles = 6 vertices starting at offset 0 in the buffer glDrawArrays(GL_TRIANGLES, 0, 6); m_verticesBuffer.release(); m_textureVerticesBuffer.release(); m_texture.release(); m_shader->release(); m_vao.release(); } void KisGLImageWidget::loadImage(const KisGLImageF16 &image) { if (m_sourceImage != image) { m_sourceImage = image; } m_havePendingTextureUpdate = true; updateGeometry(); update(); } void KisGLImageWidget::paintEvent(QPaintEvent *event) { QOpenGLWidget::paintEvent(event); } void KisGLImageWidget::resizeEvent(QResizeEvent *event) { updateVerticesBuffer(QRect(QPoint(),event->size())); QOpenGLWidget::resizeEvent(event); } QSize KisGLImageWidget::sizeHint() const { return m_sourceImage.size(); } diff --git a/plugins/dockers/smallcolorselector/KisGLImageWidget.h b/plugins/dockers/smallcolorselector/KisGLImageWidget.h index fe2a4ccd97..391642aa88 100644 --- a/plugins/dockers/smallcolorselector/KisGLImageWidget.h +++ b/plugins/dockers/smallcolorselector/KisGLImageWidget.h @@ -1,70 +1,72 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISGLIMAGEWIDGET_H #define KISGLIMAGEWIDGET_H #include #include #include #include #include #include #include #include class KisGLImageWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: KisGLImageWidget(QWidget *parent = nullptr); KisGLImageWidget(QSurfaceFormat::ColorSpace colorSpace, QWidget *parent = nullptr); + ~KisGLImageWidget(); + void initializeGL(); void paintGL(); void loadImage(const KisGLImageF16 &image); void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; QSize sizeHint() const; public Q_SLOTS: private Q_SLOTS: void slotOpenGLContextDestroyed(); private: void updateVerticesBuffer(const QRect &rect); private: KisGLImageF16 m_sourceImage; QScopedPointer m_shader; QOpenGLVertexArrayObject m_vao; QOpenGLBuffer m_verticesBuffer; QOpenGLBuffer m_textureVerticesBuffer; QOpenGLTexture m_texture; bool m_havePendingTextureUpdate = false; }; #endif // KISGLIMAGEWIDGET_H diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.cpp b/plugins/paintops/gridbrush/kis_grid_paintop.cpp index 990381950e..2eb2b2d28c 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop.cpp +++ b/plugins/paintops/gridbrush/kis_grid_paintop.cpp @@ -1,266 +1,265 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@gmail.com) * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_grid_paintop.h" #include "kis_grid_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BENCHMARK #include #endif KisGridPaintOp::KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_settings(static_cast(const_cast(settings.data()))) - , m_image(image) , m_node(node) { m_properties.readOptionSetting(settings); m_colorProperties.fillProperties(settings); m_xSpacing = m_properties.gridWidth * m_properties.scale; m_ySpacing = m_properties.gridHeight * m_properties.scale; m_spacing = m_xSpacing; m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter->paintColor()); m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); #ifdef BENCHMARK m_count = m_total = 0; #endif } KisGridPaintOp::~KisGridPaintOp() { delete m_painter; } KisSpacingInformation KisGridPaintOp::paintAt(const KisPaintInformation& info) { #ifdef BENCHMARK QTime time; time.start(); #endif KisRandomSourceSP randomSource = info.randomSource(); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); m_dab->clear(); qreal gridWidth = m_properties.gridWidth * m_properties.scale * additionalScale; qreal gridHeight = m_properties.gridHeight * m_properties.scale * additionalScale; int divide; if (m_properties.pressureDivision) { divide = m_properties.divisionLevel * info.pressure(); } else { divide = m_properties.divisionLevel; } divide = qRound(m_properties.scale * divide); qreal posX = info.pos().x(); qreal posY = info.pos().y(); posX = posX - std::fmod(posX, gridWidth); posY = posY - std::fmod(posY, gridHeight); const QRectF dabRect(posX, posY, gridWidth, gridHeight); const QRect dabRectAligned = dabRect.toAlignedRect(); divide = qMax(1, divide); const qreal yStep = gridHeight / (qreal)divide; const qreal xStep = gridWidth / (qreal)divide; QRectF tile; KoColor color(painter()->paintColor()); QScopedPointer colorPicker; if (m_node) { colorPicker.reset(new KisCrossDeviceColorPicker(m_node->paintDevice(), color)); } qreal vertBorder = m_properties.vertBorder * additionalScale; qreal horzBorder = m_properties.horizBorder * additionalScale; if (m_properties.randomBorder) { if (vertBorder == horzBorder) { vertBorder = horzBorder = vertBorder * randomSource->generateNormalized(); } else { vertBorder *= randomSource->generateNormalized(); horzBorder *= randomSource->generateNormalized(); } } bool shouldColor = true; // fill the tile if (m_colorProperties.fillBackground) { m_dab->fill(dabRectAligned, painter()->backgroundColor()); } for (int y = 0; y < divide; y++) { for (int x = 0; x < divide; x++) { // determine the tile size tile = QRectF(dabRect.x() + x * xStep, dabRect.y() + y * yStep, xStep, yStep); tile.adjust(vertBorder, horzBorder, -vertBorder, -horzBorder); tile = tile.normalized(); // do color transformation if (shouldColor) { if (colorPicker && m_colorProperties.sampleInputColor) { colorPicker->pickOldColor(tile.center().x(), tile.center().y(), color.data()); } // mix the color with background color if (m_colorProperties.mixBgColor) { KoMixColorsOp * mixOp = source()->colorSpace()->mixColorsOp(); const quint8 *colors[2]; colors[0] = color.data(); colors[1] = painter()->backgroundColor().data(); qint16 colorWeights[2]; int MAX_16BIT = 255; qreal blend = info.pressure(); colorWeights[0] = static_cast(blend * MAX_16BIT); colorWeights[1] = static_cast((1.0 - blend) * MAX_16BIT); mixOp->mixColors(colors, colorWeights, 2, color.data()); } if (m_colorProperties.useRandomHSV) { QHash params; params["h"] = (m_colorProperties.hue / 180.0) * randomSource->generateNormalized(); params["s"] = (m_colorProperties.saturation / 100.0) * randomSource->generateNormalized(); params["v"] = (m_colorProperties.value / 100.0) * randomSource->generateNormalized(); KoColorTransformation* transfo; transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", params); transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option. transfo->setParameter(4, false);//sets the colorize to false. transfo->transform(color.data(), color.data() , 1); } if (m_colorProperties.useRandomOpacity) { const qreal alpha = randomSource->generateNormalized(); color.setOpacity(alpha); m_painter->setOpacity(qRound(alpha * OPACITY_OPAQUE_U8)); } if (!m_colorProperties.colorPerParticle) { shouldColor = false; } m_painter->setPaintColor(color); } // paint some element switch (m_properties.shape) { case 0: { m_painter->paintEllipse(tile); break; } case 1: { // anti-aliased version //m_painter->paintRect(tile); m_dab->fill(tile.topLeft().x(), tile.topLeft().y(), tile.width(), tile.height(), color.data()); break; } case 2: { m_painter->drawDDALine(tile.topRight(), tile.bottomLeft()); break; } case 3: { m_painter->drawLine(tile.topRight(), tile.bottomLeft()); break; } case 4: { m_painter->drawThickLine(tile.topRight(), tile.bottomLeft() , 1, 10); break; } default: { break; } } if (m_colorProperties.colorPerParticle){ color=painter()->paintColor();//reset color// } } } QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); #ifdef BENCHMARK int msec = time.elapsed(); dbgKrita << msec << " ms/dab " << "[average: " << m_total / (qreal)m_count << "]"; m_total += msec; m_count++; #endif return computeSpacing(additionalScale); } KisSpacingInformation KisGridPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return computeSpacing(KisLodTransform::lodToScale(painter()->device())); } KisSpacingInformation KisGridPaintOp::computeSpacing(qreal lodScale) const { return KisSpacingInformation(m_spacing * lodScale); } void KisGridProperties::readOptionSetting(const KisPropertiesConfigurationSP setting) { gridWidth = qMax(1, setting->getInt(GRID_WIDTH)); gridHeight = qMax(1, setting->getInt(GRID_HEIGHT)); divisionLevel = qMax(1, setting->getInt(GRID_DIVISION_LEVEL)); pressureDivision = setting->getBool(GRID_PRESSURE_DIVISION); randomBorder = setting->getBool(GRID_RANDOM_BORDER); scale = qMax(0.1, setting->getDouble(GRID_SCALE)); vertBorder = setting->getDouble(GRID_VERTICAL_BORDER); horizBorder = setting->getDouble(GRID_HORIZONTAL_BORDER); shape = setting->getInt(GRIDSHAPE_SHAPE); } diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.h b/plugins/paintops/gridbrush/kis_grid_paintop.h index 847629ae54..1f78357f2e 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop.h +++ b/plugins/paintops/gridbrush/kis_grid_paintop.h @@ -1,88 +1,87 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@gmail.com) * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_GRID_PAINTOP_H_ #define KIS_GRID_PAINTOP_H_ //#define BENCHMARK #include #include #include #include #include "kis_grid_paintop_settings.h" class KisPainter; class KisGridProperties { public: quint16 gridWidth; quint16 gridHeight; quint16 divisionLevel; bool pressureDivision; bool randomBorder; qreal scale; qreal vertBorder; qreal horizBorder; quint8 shape; public: void readOptionSetting(const KisPropertiesConfigurationSP setting); void writeOptionSetting(KisPropertiesConfigurationSP setting); }; class KisGridPaintOp : public KisPaintOp { public: KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisGridPaintOp() override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: KisSpacingInformation computeSpacing(qreal lodScale) const; private: KisGridPaintOpSettingsSP m_settings; - KisImageWSP m_image; KisPaintDeviceSP m_dab; KisPainter* m_painter; qreal m_xSpacing; qreal m_ySpacing; qreal m_spacing; KisGridProperties m_properties; KisColorProperties m_colorProperties; KisNodeSP m_node; #ifdef BENCHMARK int m_total; int m_count; #endif }; #endif // KIS_GRID_PAINTOP_H_ diff --git a/plugins/paintops/hatching/kis_hatching_paintop.cpp b/plugins/paintops/hatching/kis_hatching_paintop.cpp index 96df6a45f9..e9bc73e5b0 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop.cpp @@ -1,214 +1,213 @@ /* * Copyright (c) 2008,2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hatching_paintop.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisHatchingPaintOp::KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) - , m_image(image) { Q_UNUSED(node); m_settings = new KisHatchingPaintOpSettings(); static_cast(settings.data())->initializeTwin(m_settings); m_hatchingBrush = new HatchingBrush(m_settings); m_angleOption.readOptionSetting(settings); m_crosshatchingOption.readOptionSetting(settings); m_separationOption.readOptionSetting(settings); m_thicknessOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_angleOption.resetAllSensors(); m_crosshatchingOption.resetAllSensors(); m_separationOption.resetAllSensors(); m_thicknessOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } KisHatchingPaintOp::~KisHatchingPaintOp() { delete m_hatchingBrush; } KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& info) { //------START SIMPLE ERROR CATCHING------- if (!painter()->device()) return KisSpacingInformation(1.0); if (!m_hatchedDab) m_hatchedDab = source()->createCompositionSourceDevice(); else m_hatchedDab->clear(); //Simple convenience renaming, I'm thinking of removing these inherited quirks KisBrushSP brush = m_brush; KisPaintDeviceSP device = painter()->device(); //Macro to catch errors Q_ASSERT(brush); //----------SIMPLE error catching code, maybe it's not even needed------ if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); //SENSOR-depending settings m_settings->anglesensorvalue = m_angleOption.apply(info); m_settings->crosshatchingsensorvalue = m_crosshatchingOption.apply(info); m_settings->separationsensorvalue = m_separationOption.apply(info); m_settings->thicknesssensorvalue = m_thicknessOption.apply(info); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const double scale = additionalScale * m_sizeOption.apply(info); if ((scale * brush->width()) <= 0.01 || (scale * brush->height()) <= 0.01) return KisSpacingInformation(1.0); KisDabShape shape(scale, 1.0, 0.0); quint8 origOpacity = m_opacityOption.apply(painter(), info); /*----Fetch the Dab----*/ static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP maskDab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); // sanity check KIS_ASSERT_RECOVER_NOOP(dstRect.size() == maskDab->bounds().size()); /*-----Convenient renaming for the limits of the maskDab, this will be used to hatch a dab of just the right size------*/ qint32 x, y, sw, sh; dstRect.getRect(&x, &y, &sw, &sh); //------This If_block pre-fills the future m_hatchedDab with a pretty backgroundColor if (m_settings->opaquebackground) { KoColor aersh = painter()->backgroundColor(); m_hatchedDab->fill(0, 0, (sw - 1), (sh - 1), aersh.data()); //this plus yellow background = french fry brush } /* If block describing how to stack hatching passes to generate crosshatching according to user specifications */ if (m_settings->enabledcurvecrosshatching) { if (m_settings->perpendicular) { if (m_settings->crosshatchingsensorvalue > 0.5) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->crosshatchingsensorvalue) * 360), painter()->paintColor(), additionalScale); } } else { if (m_settings->perpendicular) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-10), painter()->paintColor(), additionalScale); } } if (m_settings->enabledcurveangle) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->anglesensorvalue)*360+m_settings->angle), painter()->paintColor(), additionalScale); // The base hatch... unless moiré or angle if (!m_settings->moirepattern && !m_settings->enabledcurveangle) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, m_settings->angle, painter()->paintColor(), additionalScale); // The most important line, the one that paints to the screen. painter()->bitBltWithFixedSelection(x, y, m_hatchedDab, maskDab, sw, sh); painter()->renderMirrorMaskSafe(QRect(QPoint(x, y), QSize(sw, sh)), m_hatchedDab, 0, 0, maskDab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale); } KisSpacingInformation KisHatchingPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = KisLodTransform::lodToScale(painter()->device()) * m_sizeOption.apply(info); return effectiveSpacing(scale); } double KisHatchingPaintOp::spinAngle(double spin) { double tempangle = m_settings->angle + spin; qint8 factor = 1; if (tempangle < 0) factor = -1; tempangle = fabs(fmod(tempangle, 180)); if ((tempangle >= 0) && (tempangle <= 90)) return factor * tempangle; else if ((tempangle > 90) && (tempangle <= 180)) return factor * -(180 - tempangle); return 0; // this should never be executed except if NAN } diff --git a/plugins/paintops/hatching/kis_hatching_paintop.h b/plugins/paintops/hatching/kis_hatching_paintop.h index ef5fd28016..2e36a39c60 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.h +++ b/plugins/paintops/hatching/kis_hatching_paintop.h @@ -1,114 +1,113 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008, 2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_HATCHING_PAINTOP_H_ #define KIS_HATCHING_PAINTOP_H_ #include #include #include #include "hatching_brush.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include #include class KisPainter; class KisHatchingPaintOp : public KisBrushBasedPaintOp { public: KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisHatchingPaintOp() override; /** * Returns a number between -90 and 90, and corresponds to the * angle that results from adding angle 'spin' to 'm_settings->angle', * corrected to coincide with the way the GUI operates. */ double spinAngle(double spin); protected: /** * Paint a hatched dab around the mouse cursor according to * sensor settings and user preferences. */ KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: KisHatchingPaintOpSettingsSP m_settings; - KisImageWSP m_image; HatchingBrush *m_hatchingBrush; /** * PaintDevice that will be filled with a single pass of * hatching by HatchingBrush::hatch */ KisPaintDeviceSP m_hatchedDab; /** * Curve to control the hatching angle * according to user preferences set in the GUI */ KisHatchingPressureAngleOption m_angleOption; /** * Curve to control the intensity of crosshatching * according to user preferences set in the GUI */ KisHatchingPressureCrosshatchingOption m_crosshatchingOption; /** * Curve to control the dynamics of separation with * device input */ KisHatchingPressureSeparationOption m_separationOption; /** * Curve to control the thickness of the hatching lines * with device input */ KisHatchingPressureThicknessOption m_thicknessOption; /** * Curve to control the opacity of the entire dab * with device input */ KisPressureOpacityOption m_opacityOption; /** * Curve to control the size of the entire dab * with device input */ KisPressureSizeOption m_sizeOption; }; #endif // KIS_HATCHING_PAINTOP_H_ diff --git a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp index ca769fcda6..62303b796a 100644 --- a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp @@ -1,159 +1,159 @@ /* * Copyright (c) 2005 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2013 Somsubhra Bairi * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_clipboard_brush_widget.h" #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_clipboard.h" #include "kis_paint_device.h" #include "kis_gbr_brush.h" #include "KisBrushServerProvider.h" KisClipboardBrushWidget::KisClipboardBrushWidget(QWidget *parent, const QString &caption, KisImageWSP image) - : KisWdgClipboardBrush(parent), m_image(image) + : KisWdgClipboardBrush(parent) { setWindowTitle(caption); preview->setScaledContents(true); preview->setFixedSize(preview->size()); preview->setStyleSheet("border: 2px solid #222; border-radius: 4px; padding: 5px; font: normal 10px;"); m_rServer = KisBrushServerProvider::instance()->brushServer(); m_brush = 0; m_clipboard = KisClipboard::instance(); connect(m_clipboard, SIGNAL(clipChanged()), this, SLOT(slotCreateBrush())); connect(colorAsmask, SIGNAL(toggled(bool)), this, SLOT(slotUpdateUseColorAsMask(bool))); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotAddPredefined())); spacingWidget->setSpacing(true, 1.0); connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); } KisClipboardBrushWidget::~KisClipboardBrushWidget() { } void KisClipboardBrushWidget::slotCreateBrush() { // do nothing if it's hidden otherwise it can break the active brush is something is copied if (m_clipboard->hasClip() && !isHidden()) { pd = m_clipboard->clip(QRect(0, 0, 0, 0), false); //Weird! Don't know how this works! if (pd) { QRect rc = pd->exactBounds(); m_brush = KisBrushSP(new KisGbrBrush(pd, rc.x(), rc.y(), rc.width(), rc.height())); m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush->setFilename(TEMPORARY_CLIPBOARD_BRUSH_FILENAME); m_brush->setName(TEMPORARY_CLIPBOARD_BRUSH_NAME); m_brush->setValid(true); preview->setPixmap(QPixmap::fromImage(m_brush->image())); } } else { preview->setText(i18n("Nothing copied\n to Clipboard")); } if (!m_brush) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } else { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); colorAsmask->setChecked(true); // initializing this has to happen here since we need a valid brush for it to work } } void KisClipboardBrushWidget::slotSpacingChanged() { if (m_brush) { m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); } } void KisClipboardBrushWidget::showEvent(QShowEvent *) { slotCreateBrush(); } void KisClipboardBrushWidget::slotUpdateUseColorAsMask(bool useColorAsMask) { if (m_brush) { static_cast(m_brush.data())->setUseColorAsMask(useColorAsMask); preview->setPixmap(QPixmap::fromImage(m_brush->brushTipImage())); } } void KisClipboardBrushWidget::slotAddPredefined() { if(!m_brush) return; QString dir = KoResourcePaths::saveLocation("data", ResourceType::Brushes); QString extension = ".gbr"; QString name = nameEdit->text(); QString tempFileName; QFileInfo fileInfo; fileInfo.setFile(dir + name + extension); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(dir + name + QString("%1").arg(i) + extension); i++; } tempFileName = fileInfo.filePath(); if (m_rServer) { KisGbrBrushSP resource = m_brush->clone().dynamicCast(); resource->setFilename(tempFileName); if (nameEdit->text().isEmpty()) { resource->setName(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm")); } else { resource->setName(name); } if (colorAsmask->isChecked()) { resource->makeMaskImage(); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } close(); } #include "moc_kis_clipboard_brush_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.h b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.h index 63d4345740..4e767e9c10 100644 --- a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.h +++ b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.h @@ -1,75 +1,74 @@ /* * Copyright (c) 2005 Bart Coppens * Copyright (c) 2013 Somsubhra Bairi * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CLIPBOARD_BRUSH_WIDGET_H #define KIS_CLIPBOARD_BRUSH_WIDGET_H #include #include #include #include #include #include "ui_wdgclipboardbrush.h" const QString TEMPORARY_CLIPBOARD_BRUSH_FILENAME = "/tmp/temporaryClipboardBrush.gbr"; const QString TEMPORARY_CLIPBOARD_BRUSH_NAME = "Temporary clipboard brush"; const double DEFAULT_CLIPBOARD_BRUSH_SPACING = 0.25; class KisClipboard; class KoResource; class KisWdgClipboardBrush : public QDialog, public Ui::KisWdgClipboardBrush { Q_OBJECT public: KisWdgClipboardBrush(QWidget* parent) : QDialog(parent) { setupUi(this); } }; class KisClipboardBrushWidget : public KisWdgClipboardBrush { Q_OBJECT public: KisClipboardBrushWidget(QWidget* parent, const QString& caption, KisImageWSP image); virtual ~KisClipboardBrushWidget(); private Q_SLOTS: void slotCreateBrush(); void slotSpacingChanged(); void slotUpdateUseColorAsMask(bool useColorAsMask); void slotAddPredefined(); protected: void showEvent(QShowEvent *); Q_SIGNALS: void sigNewPredefinedBrush(KoResourceSP ); private: KisClipboard* m_clipboard; KisPaintDeviceSP pd; - KisImageWSP m_image; KisBrushSP m_brush; KoResourceServer *m_rServer; }; #endif // KIS_CLIPBOARD_BRUSH_WIDGET_H diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp index dbc26d0c9b..00a71b69a0 100644 --- a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp @@ -1,179 +1,178 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_roundmarkerop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_marker_painter.h" #include "kis_paintop_utils.h" KisRoundMarkerOp::KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_firstRun(true) - , m_image(image) , m_lastRadius(1.0) { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_markerOption.readOptionSetting(*settings); m_sizeOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_spacingOption.resetAllSensors(); } KisRoundMarkerOp::~KisRoundMarkerOp() { } KisSpacingInformation KisRoundMarkerOp::paintAt(const KisPaintInformation& info) { // Simple error catching if (!painter()->device()) { return KisSpacingInformation(1.0); } // get the scaling factor calculated by the size option const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = m_sizeOption.apply(info) * lodScale; const qreal rotation = 0; // TODO const qreal diameter = m_markerOption.diameter * scale; qreal radius = 0.5 * diameter; if (KisPaintOpUtils::checkSizeTooSmall(scale, diameter, diameter)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF pos = info.pos(); KisMarkerPainter gc(painter()->device(), painter()->paintColor()); if (m_firstRun) { const QVector points = painter()->calculateAllMirroredPoints(pos); Q_FOREACH(const QPointF &pt, points) { gc.fillFullCircle(pt, radius); } } else { const QVector> pairs = painter()->calculateAllMirroredPoints(qMakePair(m_lastPaintPos, pos)); Q_FOREACH(const auto &pair, pairs) { gc.fillCirclesDiff(pair.first, m_lastRadius, pair.second, radius); } } m_firstRun = false; m_lastPaintPos = pos; m_lastRadius = radius; QRectF dirtyRect(pos.x() - radius, pos.y() - radius, 2 * radius, 2 * radius); dirtyRect = kisGrowRect(dirtyRect, 1); const QVector allDirtyRects = painter()->calculateAllMirroredRects(dirtyRect.toAlignedRect()); painter()->addDirtyRects(allDirtyRects); // QPointF scatteredPos = // m_scatterOption.apply(info, // brush->maskWidth(shape, 0, 0, info), // brush->maskHeight(shape, 0, 0, info)); //updateMask(info, scale, rotation, scatteredPos); //QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ //QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); //m_lastPaintPos = newCenterPos; KisSpacingInformation spacingInfo = computeSpacing(info, diameter); if (m_firstRun) { m_firstRun = false; return spacingInfo; } return spacingInfo; } KisSpacingInformation KisRoundMarkerOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal diameter = m_markerOption.diameter * m_sizeOption.apply(info) * lodScale; return computeSpacing(info, diameter); } KisSpacingInformation KisRoundMarkerOp::computeSpacing(const KisPaintInformation &info, qreal diameter) const { const qreal rotation = 0; // TODO const bool axesFlipped = false; // TODO qreal extraSpacingScale = 1.0; if (m_spacingOption.isChecked()) { extraSpacingScale = m_spacingOption.apply(info); } return KisPaintOpUtils::effectiveSpacing(diameter, diameter, extraSpacingScale, true, true, rotation, axesFlipped, m_markerOption.spacing, m_markerOption.use_auto_spacing, m_markerOption.auto_spacing_coeff, KisLodTransform::lodToScale(painter()->device())); } diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.h b/plugins/paintops/roundmarker/kis_roundmarkerop.h index 977508a6f2..caa5b6869e 100644 --- a/plugins/paintops/roundmarker/kis_roundmarkerop.h +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.h @@ -1,60 +1,59 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_ROUNDMARKEROP_H_ #define _KIS_ROUNDMARKEROP_H_ #include #include #include #include #include #include "kis_roundmarker_option.h" class QPointF; class KisPaintOpSettings; class KisPainter; class KisRoundMarkerOp: public KisPaintOp { public: KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisRoundMarkerOp() override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal diameter) const; private: bool m_firstRun; - KisImageSP m_image; KisPaintDeviceSP m_tempDev; KisPressureSizeOption m_sizeOption; KisPressureSpacingOption m_spacingOption; QPointF m_lastPaintPos; qreal m_lastRadius; RoundMarkerOption m_markerOption; }; #endif // _KIS_ROUNDMARKEROP_H_ diff --git a/plugins/python/highpass/__init__.py b/plugins/python/highpass/__init__.py index f1ccabc909..eff2f1b189 100644 --- a/plugins/python/highpass/__init__.py +++ b/plugins/python/highpass/__init__.py @@ -1,2 +1,3 @@ -# let's make a module -from .highpass import * +from .highpass import HighpassExtension + +Scripter.addExtension(HighpassExtension(Krita.instance())) diff --git a/plugins/python/highpass/highpass.py b/plugins/python/highpass/highpass.py index 3ce1616b3a..9edfebf4e9 100644 --- a/plugins/python/highpass/highpass.py +++ b/plugins/python/highpass/highpass.py @@ -1,130 +1,160 @@ -''' -This script is licensed CC 0 1.0, so that you can learn from it. +# This script is licensed CC 0 1.0, so that you can learn from it. ------- CC 0 1.0 --------------- +# ------ CC 0 1.0 --------------- -The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. +# The person who associated a work with this deed has dedicated the +# work to the public domain by waiving all of his or her rights to the +# work worldwide under copyright law, including all related and +# neighboring rights, to the extent allowed by law. -You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. +# You can copy, modify, distribute and perform the work, even for +# commercial purposes, all without asking permission. -https://creativecommons.org/publicdomain/zero/1.0/legalcode -''' -import sys -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from krita import * +# https://creativecommons.org/publicdomain/zero/1.0/legalcode + +from PyQt5 import QtCore +from PyQt5.QtWidgets import ( + QCheckBox, + QComboBox, + QDialog, + QDialogButtonBox, + QFormLayout, + QMessageBox, + QSpinBox, + QVBoxLayout, +) +from krita import Extension class HighpassExtension(Extension): def __init__(self, parent): super().__init__(parent) def setup(self): pass - + def createActions(self, window): action = window.createAction("high_pass_filter", i18n("High Pass")) action.triggered.connect(self.showDialog) def showDialog(self): doc = Application.activeDocument() - if doc == None: - QMessageBox.information(Application.activeWindow().qwindow(), i18n("High Pass Filter"), i18n("There is no active image.")) + if doc is None: + QMessageBox.information( + Application.activeWindow().qwindow(), + i18n("High Pass Filter"), + i18n("There is no active image.")) return self.dialog = QDialog(Application.activeWindow().qwindow()) self.intRadius = QSpinBox() self.intRadius.setValue(10) self.intRadius.setRange(2, 200) self.cmbMode = QComboBox() - self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"]) + self.cmbMode.addItems( + ["Color", "Preserve DC", "Greyscale", + "Greyscale, Apply Chroma", "Redrobes"] + ) self.keepOriginal = QCheckBox(i18n("Keep original layer")) self.keepOriginal.setChecked(True) form = QFormLayout() form.addRow(i18n("Filter radius:"), self.intRadius) form.addRow(i18n("Mode:"), self.cmbMode) form.addRow("", self.keepOriginal) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.setStandardButtons( + QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.highpass) self.buttonBox.rejected.connect(self.dialog.reject) vbox = QVBoxLayout(self.dialog) vbox.addLayout(form) vbox.addWidget(self.buttonBox) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() def highpass(self): # XXX: Start undo macro image = Application.activeDocument() original = image.activeNode() working_layer = original # We can only highpass on paint layers if self.keepOriginal.isChecked() or original.type() != "paintlayer": working_layer = image.createNode("working", "paintlayer") - working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile()) - working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()), - 0, 0, image.width(), image.height()) - original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented + working_layer.setColorSpace( + original.colorModel(), + original.colorSpace(), + original.profile()) + working_layer.writeBytes( + original.readBytes(0, 0, image.width(), image.height()), + 0, 0, image.width(), image.height()) + + # XXX: Unimplemented: + original.parentNode().addChildNode(working_layer, original) image.setActiveNode(working_layer) colors_layer = None # if keeping colors - if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3: - colors_layer = working_layer.duplicate() # XXX: Unimplemented + if (self.cmbMode.currentIndex() == 1 + or self.cmbMode.currentIndex() == 3): + # XXX: Unimplemented: + colors_layer = working_layer.duplicate() colors_layer.setName("colors") - original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented + # XXX: Unimplemented: + original.parentNode().addChildNode(working_layer, colors_layer) # if greyscale, desature - if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3): + if (self.cmbMode.currentIndex() == 2 + or self.cmbMode.currentIndex() == 3): filter = Application.filter("desaturate") filter.apply(working_layer, 0, 0, image.width(), image.height()) # Duplicate on top and blur blur_layer = working_layer.duplicate() blur_layer.setName("blur") - original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented + # XXX: Unimplemented: + original.parentNode().addChildNode(blur_layer, working_layer) # blur filter = Application.filter("gaussian blur") filter_configuration = filter.configuration() filter_configuration.setProperty("horizRadius", self.intRadius.value()) filter_configuration.setProperty("vertRadius", self.intRadius.value()) - filter_configuration.setProperty("lockAspect", true) + filter_configuration.setProperty("lockAspect", True) filter.setConfiguration(filter_configuration) filter.apply(blur_layer, 0, 0, image.width(), image.height()) if self.cmbMode.currentIndex() <= 3: blur_layer.setBlendingMode("grain_extract") working_layer = image.mergeDown(blur_layer) - # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier. + # if preserve chroma, change set the mode to value and + # merge down with the layer we kept earlier. if self.cmbMode.currentIndex() == 3: working_layer.setBlendingMode("value") working_layer = image.mergeDown(working_layer) - # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier. + # if preserve DC, change set the mode to overlay and merge + # down with the average colour of the layer we kept + # earlier. if self.cmbMode.currentIndex() == 1: # get the average color of the entire image # clear the colors layer to the given color working_layer = image.mergeDown(working_layer) else: # Mode == 4, RedRobes image.setActiveNode(blur_layer) # Get the average color of the input layer # copy the solid colour layer # copy the blurred layer # XXX: End undo macro - -Scripter.addExtension(HighpassExtension(Krita.instance())) diff --git a/plugins/python/krita_script_starter/__init__.py b/plugins/python/krita_script_starter/__init__.py index 7a54830792..40502ced6d 100644 --- a/plugins/python/krita_script_starter/__init__.py +++ b/plugins/python/krita_script_starter/__init__.py @@ -1 +1,3 @@ -from .krita_script_starter import * \ No newline at end of file +from .krita_script_starter import KritaScriptStarter + +Scripter.addExtension(KritaScriptStarter(Krita.instance())) diff --git a/plugins/python/krita_script_starter/krita_script_starter.py b/plugins/python/krita_script_starter/krita_script_starter.py index 3373d81d62..c00405d170 100644 --- a/plugins/python/krita_script_starter/krita_script_starter.py +++ b/plugins/python/krita_script_starter/krita_script_starter.py @@ -1,410 +1,418 @@ """ -BBD's Krita script starter +BBD's Krita script starter This script does the boring stuff involved in creating a script for Krita. it creates -* a directory for the script in the correct Krita resources subdirectory, +* a directory for the script in the correct Krita resources subdirectory, * populates that directory with: --- a __init__.py file, +-- a __init__.py file, -- a skeleton file for the script proper -- a Manual.html file * creates a .desktop file for the script It also: -* correctly imports the script proper nto __init__.py, creates +* correctly imports the script proper nto __init__.py, creates * creates basic skeleton code depending on whether the script is intended to be an extension or a docker * creates skeleton code in the Manual.html file * (optionally) automatically enables the script in the Krita menu -Script can be run from the command line. This can be used to -bootstrap the script into a Krita menu entry - create a new script -called Krita Script Starter, then copy the script (and the .ui file) -into the directory you have just created, overwriting the existing -files. +Script can be run from the command line. This can be used to +bootstrap the script into a Krita menu entry - create a new script +called Krita Script Starter, then copy the script (and the .ui file) +into the directory you have just created, overwriting the existing +files. BBD 16 March 2018 """ -import os, sys +import os +import sys from PyQt5.QtCore import QStandardPaths, QSettings -from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox +from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox import PyQt5.uic as uic -try: +try: import krita CONTEXT_KRITA = True - Krita = Krita # to stop Eric ide complaining about unknown Krita EXTENSION = krita.Extension -except ImportError: # script being run in testing environment without Krita +except ImportError: + # script being run in testing environment without Krita CONTEXT_KRITA = False EXTENSION = QWidget -#TESTING = True +# TESTING = True TESTING = False MAIN_KRITA_ID = "Krita Script Starter" MAIN_KRITA_MENU_ENTRY = "Krita Script Starter" SCRIPT_NAME = "script_name" SCRIPT_COMMENT = "script_comment" KRITA_ID = "krita_id" LIBRARY_NAME = "library_name" MENU_ENTRY = "menu_entry" -SCRIPT_TYPE = "script_type" # extension v docker +SCRIPT_TYPE = "script_type" # extension v docker PYTHON_FILE_NAME = "python_file" CLASS_NAME = "class_name" # from LIBRARY_NAME get: # the name of the directory # the name of the main python file -# the name of the class +# the name of the class SCRIPT_EXTENSION = "Extension" SCRIPT_DOCKER = "Docker`" SCRIPT_SETTINGS = 'python' UI_FILE = "bbdkss.ui" + def load_ui(ui_file): - """ if this script has been distributed with a ui file in the same directory, - then find that directory (since it will likely be different from krita's current - working directory) and use that to load the ui file + """If this script has been distributed with a ui file in the same + directory, then find that directory (since it will likely be + different from krita's current working directory) and use that to + load the ui file. return the loaded ui """ abs_path = os.path.dirname(os.path.realpath(__file__)) ui_file = os.path.join(abs_path, UI_FILE) return uic.loadUi(ui_file) DESKTOP_TEMPLATE = """[Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library={library_name} X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name={script_name} Comment={script_comment} """ -INIT_TEMPLATE = """from .{library_name} import *""" +INIT_TEMPLATE = """from .{library_name} import {class_name} -EXTENSION_TEMPLATE = """#BBD's Krita Script Starter Feb 2018 +# And add the extension to Krita's list of extensions: +app = Krita.instance() +# Instantiate your class: +extension = {class_name}(parent=app) +app.addExtension(extension) +""" + +EXTENSION_TEMPLATE = """# BBD's Krita Script Starter Feb 2018 from krita import Extension EXTENSION_ID = '{krita_id}' MENU_ENTRY = '{menu_entry}' + class {class_name}(Extension): def __init__(self, parent): - #Always initialise the superclass, This is necessary to create the underlying C++ object + # Always initialise the superclass. + # This is necessary to create the underlying C++ object super().__init__(parent) def setup(self): pass - + def createActions(self, window): action = window.createAction(EXTENSION_ID, MENU_ENTRY, "tools/scripts") - # parameter 1 = the name that Krita uses to identify the action + # parameter 1 = the name that Krita uses to identify the action # parameter 2 = the text to be added to the menu entry for this script # parameter 3 = location of menu entry - action.triggered.connect(self.action_triggered) - - def action_triggered(self): - pass # your active code goes here. + action.triggered.connect(self.action_triggered) -# And add the extension to Krita's list of extensions: -app=Krita.instance() -extension={class_name}(parent=app) #instantiate your class -app.addExtension(extension) + def action_triggered(self): + pass # your active code goes here. """ DOCKER_TEMPLATE = """#BBD's Krita Script Starter Feb 2018 from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase DOCKER_NAME = '{script_name}' DOCKER_ID = '{krita_id}' + class {class_name}(DockWidget): def __init__(self): super().__init__() - self.setWindowTitle(DOCKER_NAME) + self.setWindowTitle(DOCKER_NAME) def canvasChanged(self, canvas): pass + instance = Krita.instance() -dock_widget_factory = DockWidgetFactory(DOCKER_ID, - DockWidgetFactoryBase.DockRight, - {class_name}) +dock_widget_factory = DockWidgetFactory(DOCKER_ID, + DockWidgetFactoryBase.DockRight, + {class_name}) instance.addDockWidgetFactory(dock_widget_factory) """ -MANUAL_TEMPLATE = """ +MANUAL_TEMPLATE = """ {script_name}

{script_name}

-Tell people about what your script does here. This is an html document so you can format it with html tags. +Tell people about what your script does here. +This is an html document so you can format it with html tags.

Usage

-Tell people how to use your script here. +Tell people how to use your script here. """ + class KritaScriptStarter(EXTENSION): def __init__(self, parent): super().__init__(parent) def setup(self): - self.script_abs_path = os.path.dirname(os.path.realpath(__file__)) - self.ui_file = os.path.join(self.script_abs_path, UI_FILE) + self.script_abs_path = os.path.dirname(os.path.realpath(__file__)) + self.ui_file = os.path.join(self.script_abs_path, UI_FILE) self.ui = load_ui(self.ui_file) - + self.ui.e_name_of_script.textChanged.connect(self.name_change) self.ui.cancel_button.clicked.connect(self.cancel) self.ui.create_button.clicked.connect(self.create) - + app_data_location = QStandardPaths.AppDataLocation target_directory = QStandardPaths.writableLocation(app_data_location) if not CONTEXT_KRITA: target_directory = os.path.join(target_directory, "krita") target_directory = os.path.join(target_directory, "pykrita") self.target_directory = target_directory -# if CONTEXT_KRITA: -# app = Krita.instance() -# action = app.createAction(MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY) -# # parameter 1 = the name that Krita uses to identify the action -# # parameter 2 = the text to be added to the menu entry for this script -# action.triggered.connect(self.action_triggered) - - def createActions(self, window): + def createActions(self, window): """ Called by Krita to create actions.""" - action = window.createAction(MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY, "tools/scripts") - # parameter 1 = the name that Krita uses to identify the action + action = window.createAction( + MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY, "tools/scripts") + # parameter 1 = the name that Krita uses to identify the action # parameter 2 = the text to be added to the menu entry for this script # parameter 3 = location of menu entry action.triggered.connect(self.action_triggered) - + def action_triggered(self): self.ui.show() self.ui.activateWindow() def cancel(self): self.ui.close() - + def create(self): """Go ahead and create the relevant files. """ - - if self.ui.e_name_of_script.text().strip() == "": - #Don't create script with empty name + + if self.ui.e_name_of_script.text().strip() == "": + # Don't create script with empty name return - - def full_dir(path): #convenience function - return os.path.join(self.target_directory, path) - + + def full_dir(path): + # convenience function + return os.path.join(self.target_directory, path) + # should already be done, but just in case: self.calculate_file_names(self.ui.e_name_of_script.text()) - - menu_entry = self.ui.e_menu_entry.text() + + menu_entry = self.ui.e_menu_entry.text() if menu_entry.strip() == "": menu_entry = self.ui.e_name_of_script.text() - + comment = self.ui.e_comment.text() - if comment.strip()=="": + if comment.strip() == "": comment = "Replace this text with your description" - - values = {SCRIPT_NAME : self.ui.e_name_of_script.text(), - SCRIPT_COMMENT : comment, - KRITA_ID : "pykrita_"+self.package_name, - SCRIPT_TYPE :SCRIPT_DOCKER if self.ui.rb_docker.isChecked() else SCRIPT_EXTENSION, - MENU_ENTRY:menu_entry, - LIBRARY_NAME : self.package_name, - CLASS_NAME: self.class_name + + values = { + SCRIPT_NAME: self.ui.e_name_of_script.text(), + SCRIPT_COMMENT: comment, + KRITA_ID: "pykrita_" + self.package_name, + SCRIPT_TYPE: SCRIPT_DOCKER if self.ui.rb_docker.isChecked() else SCRIPT_EXTENSION, # noqa: E501 + MENU_ENTRY: menu_entry, + LIBRARY_NAME: self.package_name, + CLASS_NAME: self.class_name } - + try: # create package directory package_directory = full_dir(self.package_name) # needs to be lowercase and no spaces os.mkdir(package_directory) except FileExistsError: - pass # if package directory exists write into it, overwriting existing files. + # if package directory exists write into it, overwriting + # existing files. + pass - # create desktop file + # create desktop file fn = full_dir(self.desktop_fn) - with open(fn, 'w+t') as f: + with open(fn, 'w+t') as f: f.write(DESKTOP_TEMPLATE.format(**values)) - - fn = full_dir(self.init_name) - with open(fn, 'w+t') as f: + + fn = full_dir(self.init_name) + with open(fn, 'w+t') as f: f.write(INIT_TEMPLATE.format(**values)) - + # create main package file - fn = full_dir(self.package_file) - + fn = full_dir(self.package_file) + if self.ui.rb_docker.isChecked(): - with open(fn, 'w+t') as f: + with open(fn, 'w+t') as f: f.write(DOCKER_TEMPLATE.format(**values)) else: # Default to extension type - with open(fn, 'w+t') as f: + with open(fn, 'w+t') as f: f.write(EXTENSION_TEMPLATE.format(**values)) - + # create manual file. fn = full_dir(self.manual_file) with open(fn, 'w+t') as f: f.write(MANUAL_TEMPLATE.format(**values)) # enable script in krita settings (!) - + if self.ui.cb_enable_script.isChecked(): - configPath = QStandardPaths.writableLocation(QStandardPaths.GenericConfigLocation) - configPath = os.path.join(configPath, 'kritarc' ) + configPath = QStandardPaths.writableLocation( + QStandardPaths.GenericConfigLocation) + configPath = os.path.join(configPath, 'kritarc') settings = QSettings(configPath, QSettings.IniFormat) settings.beginGroup(SCRIPT_SETTINGS) settings.setValue('enable_'+self.package_name, 'true') settings.endGroup() settings.sync() # notify success # Assemble message title = "Krita Script files created" message = [] message.append("

Directory

") - message.append("Project files were created in the directory

%s"%self.target_directory) - message.append("

Files Created

The following files were created:

") + message.append("Project files were created in the directory

%s" + % self.target_directory) + message.append( + "

Files Created

The following files were created:

") for f in self.files: - message.append("%s

"%f) - message.append("%s

"%self.manual_file) + message.append("%s

" % f) + message.append("%s

" % self.manual_file) message.append("

Location of script

") message.append("Open this file to edit your script:

") - script_path = os.path.join(self.target_directory, self.package_file) - message.append("%s

"%script_path) + script_path = os.path.join(self.target_directory, self.package_file) + message.append("%s

" % script_path) message.append("Open this file to edit your Manual:

") - script_path = os.path.join(self.target_directory, self.manual_file) - message.append("%s

"%script_path) + script_path = os.path.join(self.target_directory, self.manual_file) + message.append("%s

" % script_path) message.append("

Record these locations

") - message.append("Make a note of these locations before you click ok.

") + message.append( + "Make a note of these locations before you click ok.

") message = "\n".join(message) - + # Display message box if CONTEXT_KRITA: msgbox = QMessageBox() else: msgbox = QMessageBox(self) msgbox.setWindowTitle(title) msgbox.setText(message) msgbox.setStandardButtons(QMessageBox.Ok) msgbox.setDefaultButton(QMessageBox.Ok) msgbox.setIcon(QMessageBox.Information) msgbox.exec() - + self.ui.close() - def name_change(self, text): + def name_change(self, text): """ - name of script has changed, + name of script has changed, * calculate consequential names * update the e_files_display edit """ -# text = self.e_name_of_script.text() + text = text.strip() - if len(text) == 0 : - return + if len(text) == 0: + return self.calculate_file_names(text) edit_text = self.calculate_edit_text() self.ui.e_files_display.setText(edit_text) - + def calculate_file_names(self, text): # name of class - + spam = text.split(" ") for i, s in enumerate(spam): s = s.strip() s = s.lower() try: spam[i] = s[0].upper()+s[1:] except IndexError: continue - self.class_name = "".join(spam) - + self.class_name = "".join(spam) + # Normalise library name if TESTING: - self.package_name = "bbdsss_"+text.lower().replace(" ","_") + self.package_name = "bbdsss_"+text.lower().replace(" ", "_") else: - self.package_name = text.lower().replace(" ","_") - + self.package_name = text.lower().replace(" ", "_") + # create desktop file self.desktop_fn = self.package_name+'.desktop' - - self.init_name = os.path.join(self.package_name, "__init__.py") - self.package_file = os.path.join(self.package_name, self.package_name+".py") - self.manual_file = os.path.join(self.package_name, "Manual.html") - self.files =[self.desktop_fn, self.init_name, - self.package_name, self.package_file, - self.manual_file] + + self.init_name = os.path.join(self.package_name, "__init__.py") + self.package_file = os.path.join( + self.package_name, self.package_name + ".py") + self.manual_file = os.path.join(self.package_name, "Manual.html") + self.files = [self.desktop_fn, self.init_name, + self.package_name, self.package_file, + self.manual_file] def calculate_edit_text(self): """ - Determine if any of the intended files will collide with existing files. - + Determine if any of the intended files will collide with existing files. + """ - conflict_template = """%s - FILE ALREADY EXISTS

""" + conflict_template = "%s - FILE ALREADY EXISTS

" non_conflict_template = "%s

" file_conflict = False - + html = ["

Will create the following files:

"] for path in self.files: - target_file = os.path.join(self.target_directory, path) + target_file = os.path.join(self.target_directory, path) if os.path.exists(path): - html.append(conflict_template%target_file) + html.append(conflict_template % target_file) file_conflict = True else: - html.append(non_conflict_template%target_file) - + html.append(non_conflict_template % target_file) + if file_conflict: html.append("""

- Warning:

+ Warning:

- One or more of the files to be created already exists. - If you click "Create Script" those files will be deleted and new files - created in their place.

""") - + One or more of the files to be created already exists. + If you click "Create Script" those files will be deleted + and new files created in their place.

""") + return "\n".join(html) - -if __name__ == "__main__": - # this includes when the script is run from the command line or + + +if __name__ == "__main__": + # this includes when the script is run from the command line or # from the Scripter plugin. if CONTEXT_KRITA: # scripter plugin - # give up - has the wrong context to create widgets etc. - # maybe in the future change this. + # give up - has the wrong context to create widgets etc. + # maybe in the future change this. pass else: app = QApplication([]) - + extension = KritaScriptStarter(None) extension.setup() extension.action_triggered() sys.exit(app.exec_()) - -elif CONTEXT_KRITA: - # And add the extension to Krita's list of extensions: - app=Krita.instance() - extension=KritaScriptStarter(parent=app) #instantiate your class - app.addExtension(extension) - diff --git a/plugins/python/scriptdocker/__init__.py b/plugins/python/scriptdocker/__init__.py index 96bc1528e4..c3167d7250 100644 --- a/plugins/python/scriptdocker/__init__.py +++ b/plugins/python/scriptdocker/__init__.py @@ -1,2 +1,8 @@ -# let's make a module -from .scriptdocker import * +import krita +from .scriptdocker import ScriptDocker + +Application.addDockWidgetFactory( + krita.DockWidgetFactory("scriptdocker", + krita.DockWidgetFactoryBase.DockRight, + ScriptDocker) +) diff --git a/plugins/python/scriptdocker/scriptdocker.py b/plugins/python/scriptdocker/scriptdocker.py index 3b865c610a..67acce88c8 100644 --- a/plugins/python/scriptdocker/scriptdocker.py +++ b/plugins/python/scriptdocker/scriptdocker.py @@ -1,66 +1,67 @@ -''' -This script is licensed CC 0 1.0, so that you can learn from it. +# This script is licensed CC 0 1.0, so that you can learn from it. ------- CC 0 1.0 --------------- +# ------ CC 0 1.0 --------------- -The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. +# The person who associated a work with this deed has dedicated the +# work to the public domain by waiving all of his or her rights to the +# work worldwide under copyright law, including all related and +# neighboring rights, to the extent allowed by law. -You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. +# You can copy, modify, distribute and perform the work, even for +# commercial purposes, all without asking permission. -https://creativecommons.org/publicdomain/zero/1.0/legalcode -''' -rom PyQt5.QtWidgets import (QWidget, QVBoxLayout, QListView, QFormLayout, - QHBoxLayout, QPushButton, QLineEdit, QListWidget) -from PyQt5.QtCore import QObject +# https://creativecommons.org/publicdomain/zero/1.0/legalcode + +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QFormLayout, + QHBoxLayout, QPushButton, QLineEdit) import krita class ScriptDocker(krita.DockWidget): def __init__(self): super(ScriptDocker, self).__init__() self.baseWidget = QWidget() self.layout = QVBoxLayout() self.scriptsLayout = QFormLayout() self.addButton = QPushButton(i18n("Add Script")) self.actions = [] self.layout.addLayout(self.scriptsLayout) self.layout.addWidget(self.addButton) self.baseWidget.setLayout(self.layout) self.setWidget(self.baseWidget) self.setWindowTitle(i18n("Script Docker")) self.addButton.clicked.connect(self.addNewRow) def canvasChanged(self, canvas): pass def addNewRow(self): directorySelectorLayout = QHBoxLayout() directoryTextField = QLineEdit() directoryDialogButton = QPushButton(i18n("...")) directoryDialogButton.clicked.connect(self.test) directorySelectorLayout.addWidget(directoryTextField) directorySelectorLayout.addWidget(directoryDialogButton) - self.scriptsLayout.addRow(str(i18n("Script {0}")).format(self.scriptsLayout.rowCount() + 1), directorySelectorLayout) + self.scriptsLayout.addRow( + str(i18n("Script {0}")).format(self.scriptsLayout.rowCount() + 1), + directorySelectorLayout) def test(self): obj = self.sender() print('button', obj) def loadActions(self): pass def readSettings(self): pass def writeSettings(self): pass - - -Application.addDockWidgetFactory(krita.DockWidgetFactory("scriptdocker", krita.DockWidgetFactoryBase.DockRight, ScriptDocker)) diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index 17d775f67f..ee44b28e2e 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,421 +1,428 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" #include "kis_sequential_iterator.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_layer_utils.h" #include #include TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode, KisNodeList processedNodes, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), m_selection(selection) { KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data())); m_savedRootNode = rootNode; m_savedProcessedNodes = processedNodes; } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); PreparePreviewData *ppd = dynamic_cast(data); if (ppd) { KisNodeSP rootNode = m_savedRootNode; KisNodeList processedNodes = m_savedProcessedNodes; KisPaintDeviceSP previewDevice; if (rootNode->childCount() || !rootNode->paintDevice()) { if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { previewDevice = createDeviceCache(tmask->buildPreviewDevice()); KIS_SAFE_ASSERT_RECOVER(!m_selection) { m_selection = 0; } } else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) { const QRect bounds = group->image()->bounds(); KisImageSP clonedImage = new KisImage(0, bounds.width(), bounds.height(), group->colorSpace(), "transformed_image"); KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data()); + + // In case the group is pass-through, it needs to be disabled for the preview, + // otherwise it will crash (no parent for a preview leaf). + // Also it needs to be done before setting the root layer for clonedImage. + // Result: preview for pass-through group is the same as for standard group + // (i.e. filter layers in the group won't affect the layer stack for a moment). + clonedGroup->setPassThroughMode(false); clonedImage->setRootLayer(clonedGroup); QQueue linearizedSrcNodes; KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) { linearizedSrcNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) { KisNodeSP srcNode = linearizedSrcNodes.dequeue(); if (!processedNodes.contains(srcNode)) { node->setVisible(false); } }); clonedImage->refreshGraph(); KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup); clonedImage->waitForDone(); previewDevice = createDeviceCache(clonedImage->projection()); previewDevice->setDefaultBounds(group->projection()->defaultBounds()); // we delete the cloned image in GUI thread to ensure // no signals are still pending makeKisDeleteLaterWrapper(clonedImage)->deleteLater(); } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); previewDevice = createDeviceCache(rootNode->projection()); } } else { KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); if (dynamic_cast(rootNode.data())) { KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); } previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect srcRect = cacheDevice->exactBounds(); KisSequentialConstIterator srcIt(cacheDevice, srcRect); KisSequentialIterator dstIt(previewDevice, srcRect); const int pixelSize = previewDevice->colorSpace()->pixelSize(); KisImageConfig cfg(true); KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace()); const qreal coeff = 1.0 / 255.0; const qreal baseOpacity = 0.5; while (srcIt.nextPixel() && dstIt.nextPixel()) { qreal gray = srcIt.rawDataConst()[0]; qreal alpha = srcIt.rawDataConst()[1]; pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); memcpy(dstIt.rawData(), pixel.data(), pixelSize); } } else { previewDevice = cacheDevice; } putDeviceCache(rootNode->paintDevice(), cacheDevice); } emit sigPreviewDeviceReady(previewDevice); } else if(td) { m_savedTransformArgs = td->config; if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); /** * Selection masks might have an overlay enabled, we should disable that */ if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { KisSelectionSP selection = mask->selection(); if (selection) { selection->setVisible(false); m_deactivatedSelections.append(selection); mask->setDirty(); } } } else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) { externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true); externalLayer->setDirty(); m_hiddenProjectionLeaves.append(csd->node); } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = m_savedTransformArgs; data->rootNode = m_savedRootNode; data->transformedNodes = m_savedProcessedNodes; command->setExtraData(data); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); m_deactivatedSelections.append(m_selection); } } void TransformStrokeStrategy::finishStrokeCallback() { Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void TransformStrokeStrategy::cancelStrokeCallback() { KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } }