diff --git a/3rdparty/ext_qt/0001-Use-fast-path-for-unsupported-mime-types.patch b/3rdparty/ext_qt/0001-Use-fast-path-for-unsupported-mime-types.patch new file mode 100644 index 0000000000..3e72c541a4 --- /dev/null +++ b/3rdparty/ext_qt/0001-Use-fast-path-for-unsupported-mime-types.patch @@ -0,0 +1,33 @@ +From 6644f33d9c9be580f3277792e304d20c4b973cdd Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 19 Jun 2019 15:04:31 +0300 +Subject: [PATCH 01/17] Use fast path for unsupported mime types + +We don't need to request the entire image every time +Windows asks for the list of supported MIME types. That +can make graphical applications very slow (because the image +might be quite big) + +Change-Id: I84223417661eceffa1362f8045c89e260b68e0a7 +--- + src/plugins/platforms/windows/qwindowsmime.cpp | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp +index 030d8d1e0f..b4f325736b 100644 +--- a/src/plugins/platforms/windows/qwindowsmime.cpp ++++ b/src/plugins/platforms/windows/qwindowsmime.cpp +@@ -1082,6 +1082,10 @@ bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *p + bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const + { + int cf = getCf(formatetc); ++ ++ if (cf != CF_DIBV5 && cf != CF_DIB && cf != CF_PNG) ++ return false; ++ + if (!mimeData->hasImage()) + return false; + const QImage image = qvariant_cast(mimeData->imageData()); +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch b/3rdparty/ext_qt/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch deleted file mode 100644 index 100370ce50..0000000000 --- a/3rdparty/ext_qt/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0cda446fe4dea3046cb0cea820b9934164df8f19 Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Tue, 21 Jun 2016 14:50:07 +0300 -Subject: [PATCH 01/27] Don't request the MIME image every time Windows asks - for the list of supported types - -Change-Id: I05516d83dc4e0f192bc94f92cefc722f25dae4d4 ---- - src/plugins/platforms/windows/qwindowsmime.cpp | 9 ++++++--- - 1 file changed, 6 insertions(+), 3 deletions(-) - -diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp -index 96e34fb4..ca6e0925 100644 ---- a/src/plugins/platforms/windows/qwindowsmime.cpp -+++ b/src/plugins/platforms/windows/qwindowsmime.cpp -@@ -1082,12 +1082,15 @@ bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *p - bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const - { - int cf = getCf(formatetc); -+ -+ if (cf != CF_DIBV5 && cf != CF_DIB && cf != CF_PNG) -+ return false; -+ - if (!mimeData->hasImage()) - return false; -+ - const QImage image = qvariant_cast(mimeData->imageData()); -- if (image.isNull()) -- return false; -- return cf == CF_DIBV5 || (cf == CF_DIB) || cf == int(CF_PNG); -+ return !image.isNull(); - } - - bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const --- -2.22.0.windows.1 - diff --git a/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch b/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch similarity index 86% rename from 3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch rename to 3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch index 3097b63f33..aa09c1933d 100644 --- a/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch +++ b/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch @@ -1,30 +1,30 @@ -From 025be9d8f4adcdccbc1fd4be329771cf2ef95942 Mon Sep 17 00:00:00 2001 +From 835bb62519cc53976b8341c0d83a660674f66a92 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Tue, 21 Jun 2016 14:50:47 +0300 -Subject: [PATCH 02/27] Hack: always return we support DIBV5 +Subject: [PATCH 02/17] Hack: always return we support DIBV5 Asking for the entire image may be too expensive Change-Id: I44c38fad73f1bb5859eb58b941054eeb6c3c6b66 --- src/plugins/platforms/windows/qwindowsmime.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp -index ca6e0925..cff89586 100644 +index b4f325736b..e2ae95d577 100644 --- a/src/plugins/platforms/windows/qwindowsmime.cpp +++ b/src/plugins/platforms/windows/qwindowsmime.cpp @@ -1055,9 +1055,7 @@ QVector QWindowsMimeImage::formatsForMime(const QString &mimeType, co QVector formatetcs; if (mimeData->hasImage() && mimeType == QLatin1String("application/x-qt-image")) { //add DIBV5 if image has alpha channel. Do not add CF_PNG here as it will confuse MS Office (QTBUG47656). - QImage image = qvariant_cast(mimeData->imageData()); - if (!image.isNull() && image.hasAlphaChannel()) - formatetcs += setCf(CF_DIBV5); + formatetcs += setCf(CF_DIBV5); formatetcs += setCf(CF_DIB); } if (!formatetcs.isEmpty()) -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0003-Implement-openGL-surface-color-space-selection-in-An.patch b/3rdparty/ext_qt/0003-Implement-openGL-surface-color-space-selection-in-An.patch new file mode 100644 index 0000000000..860d0ae15b --- /dev/null +++ b/3rdparty/ext_qt/0003-Implement-openGL-surface-color-space-selection-in-An.patch @@ -0,0 +1,1200 @@ +From 721279a37e70459798928babacc2ccc8bf4fca3a Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 8 Dec 2018 15:35:43 +0300 +Subject: [PATCH 03/17] Implement openGL surface color space selection in Angle + +WARNING: this patch actually means that the library must be build on + the system with at least DXGI 1.4 (DirectX 12 API) present + in SDK. Mingw64 7.3 supports that. + +1) D3D11 implementation of angle now supports GL_RGB10_A2 format + +2) Technically, Angle's EGL implementation now supports the following + display extensions: + * EGL_KHR_gl_colorspace + * EGL_EXT_gl_colorspace_scrgb_linear + * EGL_EXT_gl_colorspace_bt2020_pq + +3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute, + which allows selection one of four color modes: + * Linear --- just pass-through data to GPU + * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system + becomes clever and automatically converts linear framebuffer + attachments to sRGB space, as per EGL_KHR_gl_colorspace definition. + It is not possible to select sRGB without this extra "feature". + * scRGB --- p709-g10 color space. This mode is the only mode + supported in f16-bit mode (and it is also not supported in other + bit depths). + * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode. + +5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode: + * because non-flip mode is considered deprecated and HDR is not + supported in it; + * because in flip-discard mode partial updates from + SwapChain11::present() are not supported and return an error, + which is never checked :) + +6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because + flip modes are not available on Windows 7 and such old systems. + +Notes: + +eglCreatePixmapSurface() is not implemented in Angle, so the support is +not added. + +eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface() +do not have support for color spaces according to the extension wording +(and they are also not supported by Angle :) ) + +Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6 +--- + src/3rdparty/angle/src/common/platform.h | 14 +- + src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +- + src/3rdparty/angle/src/libANGLE/Caps.h | 9 + + .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +- + .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +- + .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 + + .../renderer/d3d/d3d11/Renderer11.cpp | 16 +- + .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +- + .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++- + .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +- + .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +- + .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +- + .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +- + .../angle/src/libANGLE/validationEGL.cpp | 53 ++ + ...-surface-color-space-selection-in-An.patch | 596 ++++++++++++++++++ + 15 files changed, 831 insertions(+), 20 deletions(-) + create mode 100644 src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch + +diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h +index fb251da579..2e17994557 100644 +--- a/src/3rdparty/angle/src/common/platform.h ++++ b/src/3rdparty/angle/src/common/platform.h +@@ -59,12 +59,14 @@ + # endif + + # if defined(ANGLE_ENABLE_D3D11) +-#include +-#include +-#include +-#include +-#include +-#include ++# include ++# include ++# include ++# include ++# include ++# include ++# include // WARNING: This is actually D3D12! ++# include + # endif + + #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11) +diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp +index 44da2bbe27..2088457458 100644 +--- a/src/3rdparty/angle/src/libANGLE/Caps.cpp ++++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp +@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions() + displayTextureShareGroup(false), + createContextClientArrays(false), + programCacheControl(false), +- robustResourceInitialization(false) ++ robustResourceInitialization(false), ++ colorspaceSRGB(false), ++ colorspaceSCRGBLinear(false), ++ colorspaceBt2020PQ(false) + { + } + +@@ -1146,6 +1149,9 @@ std::vector DisplayExtensions::getStrings() const + InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings); + InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings); + InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings); ++ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings); ++ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings); ++ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings); + // TODO(jmadill): Enable this when complete. + //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings); + // clang-format on +diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h +index 64bdf97112..8157af5800 100644 +--- a/src/3rdparty/angle/src/libANGLE/Caps.h ++++ b/src/3rdparty/angle/src/libANGLE/Caps.h +@@ -692,6 +692,15 @@ struct DisplayExtensions + + // EGL_ANGLE_robust_resource_initialization + bool robustResourceInitialization; ++ ++ // EGL_KHR_gl_colorspace ++ bool colorspaceSRGB; ++ ++ // EGL_EXT_gl_colorspace_scrgb_linear ++ bool colorspaceSCRGBLinear; ++ ++ // EGL_EXT_gl_colorspace_bt2020_pq ++ bool colorspaceBt2020PQ; + }; + + struct DeviceExtensions +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h +index dcc98f2ec6..b8ee635625 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h +@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples) = 0; ++ EGLint samples, ++ EGLint colorSpace) = 0; + virtual egl::Error getD3DTextureInfo(const egl::Config *configuration, + IUnknown *d3dTexture, + EGLint *width, +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp +index 7657aef79e..efd4dd1a24 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp +@@ -22,6 +22,27 @@ + namespace rx + { + ++GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace) ++{ ++ GLenum result = baseFormat; ++ ++ /** ++ * If sRGB extension is supported, we should change the surface format ++ * to a specific one that does support automated gamma conversion. ++ * ++ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of ++ * textures in this format technically is not supported! ++ */ ++ if (display->getExtensions().colorspaceSRGB && ++ baseFormat == GL_RGBA8_OES && ++ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR) ++ { ++ result = GL_SRGB8_ALPHA8; ++ } ++ ++ return result; ++} ++ + SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, + RendererD3D *renderer, + egl::Display *display, +@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, + mDisplay(display), + mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE), + mOrientation(static_cast(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))), +- mRenderTargetFormat(state.config->renderTargetFormat), ++ mColorSpace(static_cast(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))), ++ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)), + mDepthStencilFormat(state.config->depthStencilFormat), + mSwapChain(nullptr), + mSwapIntervalDirty(true), +@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display) + + mSwapChain = + mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat, +- mDepthStencilFormat, mOrientation, mState.config->samples); ++ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace); + if (!mSwapChain) + { + return egl::EglBadAlloc(); +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h +index 01d2573244..ccb793d423 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h +@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl + + bool mFixedSize; + GLint mOrientation; ++ EGLint mColorSpace; + + GLenum mRenderTargetFormat; + GLenum mDepthStencilFormat; +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp +index b0ef9abddc..f0e497b52f 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp +@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display) + mRenderer11DeviceCaps.supportsConstantBufferOffsets = false; + mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false; + mRenderer11DeviceCaps.supportsDXGI1_2 = false; ++ mRenderer11DeviceCaps.supportsDXGI1_4 = false; + mRenderer11DeviceCaps.B5G6R5support = 0; + mRenderer11DeviceCaps.B4G4R4A4support = 0; + mRenderer11DeviceCaps.B5G5R5A1support = 0; +@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice() + + // Gather stats on DXGI and D3D feature level + ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2); ++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4); + + ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel); + +@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps() + IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject(mDxgiAdapter); + mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr); + SafeRelease(dxgiAdapter2); ++ ++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject(mDxgiAdapter); ++ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr); ++ SafeRelease(dxgiAdapter3); + } + + gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig( +@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions + + // All D3D feature levels support robust resource init + outExtensions->robustResourceInitialization = true; ++ ++ // color space selection supported in DXGI 1.4 only ++ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4; ++ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4; ++ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4; + } + + gl::Error Renderer11::flush() +@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow, + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples) ++ EGLint samples, ++ EGLint colorSpace) + { + return new SwapChain11(this, GetAs(nativeWindow), shareHandle, d3dTexture, +- backBufferFormat, depthBufferFormat, orientation, samples); ++ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace); + } + + void *Renderer11::getD3DDevice() +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h +index a8c24e681b..3516bf779d 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h +@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps + + D3D_FEATURE_LEVEL featureLevel; + bool supportsDXGI1_2; // Support for DXGI 1.2 ++ bool supportsDXGI1_4; // Support for DXGI 1.4 + bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView + bool supportsConstantBufferOffsets; // Support for Constant buffer offset + bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader +@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples) override; ++ EGLint samples, ++ EGLint colorSpace) override; + egl::Error getD3DTextureInfo(const egl::Config *configuration, + IUnknown *d3dTexture, + EGLint *width, +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp +index dcfd06484d..fc967b90d0 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp +@@ -18,6 +18,11 @@ + #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h" + #include "third_party/trace_event/trace_event.h" + ++#if 0 ++// used only for HDR metadata configuration options ++#include ++#endif ++ + // Precompiled shaders + #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h" + #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h" +@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer, + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples) ++ EGLint samples, ++ EGLint colorSpace) + : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat), + mRenderer(renderer), + mWidth(-1), + mHeight(-1), + mOrientation(orientation), ++ mColorSpace(colorSpace), + mAppCreatedShareHandle(mShareHandle != nullptr), + mSwapInterval(0), + mPassThroughResourcesInit(false), +@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context, + mSwapChain1 = d3d11::DynamicCastComObject(mSwapChain); + } + ++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4) ++ { ++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject(mSwapChain); ++ ++ printf("*** EGL colorSpace: 0x%X\n", mColorSpace); ++ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat); ++ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat()); ++ ++ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) { ++ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; ++ switch (mColorSpace) ++ { ++ case EGL_GL_COLORSPACE_SRGB_KHR: ++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; ++ break; ++ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: ++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; ++ break; ++ case EGL_GL_COLORSPACE_BT2020_PQ_EXT: ++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; ++ break; ++ default: ++ ASSERT(0 && "Unsupported colorspace requested"); ++ } ++ ++ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace); ++ ++ UINT supported = 0; ++ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported); ++ ASSERT(SUCCEEDED(result)); ++ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { ++ SafeRelease(swapChain3); ++ return EGL_BAD_MATCH; ++ } ++ ++ result = swapChain3->SetColorSpace1(nativeColorSpace); ++ ASSERT(SUCCEEDED(result)); ++ } ++ ++ SafeRelease(swapChain3); ++ ++#if 0 ++ ++ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject(mSwapChain); ++ ++ DXGI_HDR_METADATA_HDR10 md; ++ md.RedPrimary[0] = 0.680 * 50000; ++ md.RedPrimary[1] = 0.320 * 50000; ++ md.GreenPrimary[0] = 0.265 * 50000; ++ md.GreenPrimary[1] = 0.690 * 50000; ++ md.BluePrimary[0] = 0.150 * 50000; ++ md.BluePrimary[1] = 0.060 * 50000; ++ md.WhitePoint[0] = 0.3127 * 50000; ++ md.WhitePoint[1] = 0.3290 * 50000; ++ md.MaxMasteringLuminance = 1000 * 10000; ++ md.MinMasteringLuminance = 0.001 * 10000; ++ md.MaxContentLightLevel = 1000; ++ md.MaxFrameAverageLightLevel = 200; ++ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md); ++ printf("*** Result hdr 0x%X\n", result); ++ SafeRelease(swapChain4); ++#endif ++ } ++ + ID3D11Texture2D *backbufferTex = nullptr; + result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), + reinterpret_cast(&backbufferTex)); + ASSERT(SUCCEEDED(result)); ++ ++ // TODO: recover rendering to sRGB ++ // ++ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc; ++ // offscreenRTVDesc.Format = getSwapChainNativeFormat(); ++ // ++ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) { ++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) { ++ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; ++ // } ++ // ++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) { ++ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; ++ // } ++ // } ++ // ++ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format); ++ + const auto &format = + d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps()); + mBackBufferTexture.set(backbufferTex, format); +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h +index eca068210b..2a4b9ba274 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h +@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples); ++ EGLint samples, ++ EGLint colorSpace); + ~SwapChain11() override; + + EGLint resize(const gl::Context *context, +@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D + EGLint mWidth; + EGLint mHeight; + const EGLint mOrientation; ++ EGLint mColorSpace; + bool mAppCreatedShareHandle; + unsigned int mSwapInterval; + bool mPassThroughResourcesInit; +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp +index 5394e3d3e7..af52c41d00 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp +@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, + + // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a + // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain. ++ // ++ // NOTE: in non-flip mode HDR rendering is not supported, so use it ++ // by default + IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject(factory); + if (factory2 != nullptr) + { +@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.BufferUsage = + DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER; +- swapChainDesc.BufferCount = 1; ++ swapChainDesc.BufferCount = 2; + swapChainDesc.Scaling = DXGI_SCALING_STRETCH; +- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; ++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + swapChainDesc.Flags = 0; + IDXGISwapChain1 *swapChain1 = nullptr; +@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, + } + + DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; +- swapChainDesc.BufferCount = 1; ++ swapChainDesc.BufferCount = 2; + swapChainDesc.BufferDesc.Format = format; + swapChainDesc.BufferDesc.Width = width; + swapChainDesc.BufferDesc.Height = height; +@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, + swapChainDesc.SampleDesc.Count = samples; + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.Windowed = TRUE; ++ ++ /** ++ * NOTE1: in discard mode the swap chain doesn't support partial ++ * presentatiopn with Present1() call. Though it is not a big ++ * problem, because in case DXGI 1.2 is supported this code is ++ * unreachable. ++ * ++ * NOTE2: Flip modes are not supported on Windows 7 and the like, ++ * so use a legacy mode as a fallback ++ */ + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain); +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp +index 75c6298868..58596169a8 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp +@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow, + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples) ++ EGLint samples, ++ EGLint colorSpace) + { ++ UNUSED_VARIABLE(colorSpace); + return new SwapChain9(this, GetAs(nativeWindow), shareHandle, d3dTexture, + backBufferFormat, depthBufferFormat, orientation); + } +diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h +index 9ddee45f0f..ce4bb201e5 100644 +--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h ++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h +@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D + GLenum backBufferFormat, + GLenum depthBufferFormat, + EGLint orientation, +- EGLint samples) override; ++ EGLint samples, ++ EGLint colorSpace) override; + egl::Error getD3DTextureInfo(const egl::Config *configuration, + IUnknown *d3dTexture, + EGLint *width, +diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp +index 13a3a9e280..858d7ee929 100644 +--- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp ++++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp +@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin + "either EGL_TRUE or EGL_FALSE."; + } + break; ++ case EGL_GL_COLORSPACE: ++ ++ if (!displayExtensions.colorspaceSRGB) ++ { ++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; ++ } ++ ++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) ++ { ++ if (!displayExtensions.colorspaceSCRGBLinear) ++ { ++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; ++ } ++ } ++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) ++ { ++ if (!displayExtensions.colorspaceBt2020PQ) ++ { ++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; ++ } ++ } ++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) ++ { ++ return EglBadAttribute() << "Unknown EGL color space requested"; ++ } ++ break; + + default: + return EglBadAttribute(); +@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri + } + break; + ++ case EGL_GL_COLORSPACE: ++ ++ if (!displayExtensions.colorspaceSRGB) ++ { ++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; ++ } ++ ++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) ++ { ++ if (!displayExtensions.colorspaceSCRGBLinear) ++ { ++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; ++ } ++ } ++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) ++ { ++ if (!displayExtensions.colorspaceBt2020PQ) ++ { ++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; ++ } ++ } ++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) ++ { ++ return EglBadAttribute() << "Unknown EGL color space requested"; ++ } ++ break; ++ + default: + return EglBadAttribute(); + } +diff --git a/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch +new file mode 100644 +index 0000000000..dfbe362690 +--- /dev/null ++++ b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch +@@ -0,0 +1,596 @@ ++From 05082a2affad3428e2ba4475a5c083e81a7730ab Mon Sep 17 00:00:00 2001 ++From: Dmitry Kazakov ++Date: Sat, 8 Dec 2018 15:35:43 +0300 ++Subject: [PATCH] Implement openGL surface color space selection in Angle ++ ++WARNING: this patch actually means that the library must be build on ++ the system with at least DXGI 1.4 (DirectX 12 API) present ++ in SDK. Mingw64 7.3 supports that. ++ ++1) D3D11 implementation of angle now supports GL_RGB10_A2 format ++ ++2) Technically, Angle's EGL implementation now supports the following ++ display extensions: ++ * EGL_KHR_gl_colorspace ++ * EGL_EXT_gl_colorspace_scrgb_linear ++ * EGL_EXT_gl_colorspace_bt2020_pq ++ ++3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute, ++ which allows selection one of four color modes: ++ * Linear --- just pass-through data to GPU ++ * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system ++ becomes clever and automatically converts linear framebuffer ++ attachments to sRGB space, as per EGL_KHR_gl_colorspace definition. ++ It is not possible to select sRGB without this extra "feature". ++ * scRGB --- p709-g10 color space. This mode is the only mode ++ supported in f16-bit mode (and it is also not supported in other ++ bit depths). ++ * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode. ++ ++5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode: ++ * because non-flip mode is considered deprecated and HDR is not ++ supported in it; ++ * because in flip-discard mode partial updates from ++ SwapChain11::present() are not supported and return an error, ++ which is never checked :) ++ ++6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because ++ flip modes are not available on Windows 7 and such old systems. ++ ++Notes: ++ ++eglCreatePixmapSurface() is not implemented in Angle, so the support is ++not added. ++ ++eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface() ++do not have support for color spaces according to the extension wording ++(and they are also not supported by Angle :) ) ++ ++Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6 ++--- ++ src/3rdparty/angle/src/common/platform.h | 14 +-- ++ src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +- ++ src/3rdparty/angle/src/libANGLE/Caps.h | 9 ++ ++ .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +- ++ .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +++++- ++ .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 + ++ .../renderer/d3d/d3d11/Renderer11.cpp | 16 +++- ++ .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +- ++ .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++++++++++++++++++- ++ .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +- ++ .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +++- ++ .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +- ++ .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +- ++ .../angle/src/libANGLE/validationEGL.cpp | 53 +++++++++++ ++ 14 files changed, 235 insertions(+), 20 deletions(-) ++ ++diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h ++index fb251da579..2e17994557 100644 ++--- a/src/3rdparty/angle/src/common/platform.h +++++ b/src/3rdparty/angle/src/common/platform.h ++@@ -59,12 +59,14 @@ ++ # endif ++ ++ # if defined(ANGLE_ENABLE_D3D11) ++-#include ++-#include ++-#include ++-#include ++-#include ++-#include +++# include +++# include +++# include +++# include +++# include +++# include +++# include // WARNING: This is actually D3D12! +++# include ++ # endif ++ ++ #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11) ++diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp ++index 44da2bbe27..2088457458 100644 ++--- a/src/3rdparty/angle/src/libANGLE/Caps.cpp +++++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp ++@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions() ++ displayTextureShareGroup(false), ++ createContextClientArrays(false), ++ programCacheControl(false), ++- robustResourceInitialization(false) +++ robustResourceInitialization(false), +++ colorspaceSRGB(false), +++ colorspaceSCRGBLinear(false), +++ colorspaceBt2020PQ(false) ++ { ++ } ++ ++@@ -1146,6 +1149,9 @@ std::vector DisplayExtensions::getStrings() const ++ InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings); ++ InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings); ++ InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings); +++ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings); +++ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings); +++ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings); ++ // TODO(jmadill): Enable this when complete. ++ //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings); ++ // clang-format on ++diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h ++index 64bdf97112..8157af5800 100644 ++--- a/src/3rdparty/angle/src/libANGLE/Caps.h +++++ b/src/3rdparty/angle/src/libANGLE/Caps.h ++@@ -692,6 +692,15 @@ struct DisplayExtensions ++ ++ // EGL_ANGLE_robust_resource_initialization ++ bool robustResourceInitialization; +++ +++ // EGL_KHR_gl_colorspace +++ bool colorspaceSRGB; +++ +++ // EGL_EXT_gl_colorspace_scrgb_linear +++ bool colorspaceSCRGBLinear; +++ +++ // EGL_EXT_gl_colorspace_bt2020_pq +++ bool colorspaceBt2020PQ; ++ }; ++ ++ struct DeviceExtensions ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h ++index dcc98f2ec6..b8ee635625 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h ++@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples) = 0; +++ EGLint samples, +++ EGLint colorSpace) = 0; ++ virtual egl::Error getD3DTextureInfo(const egl::Config *configuration, ++ IUnknown *d3dTexture, ++ EGLint *width, ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp ++index 7657aef79e..efd4dd1a24 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp ++@@ -22,6 +22,27 @@ ++ namespace rx ++ { ++ +++GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace) +++{ +++ GLenum result = baseFormat; +++ +++ /** +++ * If sRGB extension is supported, we should change the surface format +++ * to a specific one that does support automated gamma conversion. +++ * +++ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of +++ * textures in this format technically is not supported! +++ */ +++ if (display->getExtensions().colorspaceSRGB && +++ baseFormat == GL_RGBA8_OES && +++ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR) +++ { +++ result = GL_SRGB8_ALPHA8; +++ } +++ +++ return result; +++} +++ ++ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, ++ RendererD3D *renderer, ++ egl::Display *display, ++@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, ++ mDisplay(display), ++ mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE), ++ mOrientation(static_cast(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))), ++- mRenderTargetFormat(state.config->renderTargetFormat), +++ mColorSpace(static_cast(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))), +++ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)), ++ mDepthStencilFormat(state.config->depthStencilFormat), ++ mSwapChain(nullptr), ++ mSwapIntervalDirty(true), ++@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display) ++ ++ mSwapChain = ++ mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat, ++- mDepthStencilFormat, mOrientation, mState.config->samples); +++ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace); ++ if (!mSwapChain) ++ { ++ return egl::EglBadAlloc(); ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h ++index 01d2573244..ccb793d423 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h ++@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl ++ ++ bool mFixedSize; ++ GLint mOrientation; +++ EGLint mColorSpace; ++ ++ GLenum mRenderTargetFormat; ++ GLenum mDepthStencilFormat; ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp ++index b0ef9abddc..f0e497b52f 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp ++@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display) ++ mRenderer11DeviceCaps.supportsConstantBufferOffsets = false; ++ mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false; ++ mRenderer11DeviceCaps.supportsDXGI1_2 = false; +++ mRenderer11DeviceCaps.supportsDXGI1_4 = false; ++ mRenderer11DeviceCaps.B5G6R5support = 0; ++ mRenderer11DeviceCaps.B4G4R4A4support = 0; ++ mRenderer11DeviceCaps.B5G5R5A1support = 0; ++@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice() ++ ++ // Gather stats on DXGI and D3D feature level ++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2); +++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4); ++ ++ ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel); ++ ++@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps() ++ IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject(mDxgiAdapter); ++ mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr); ++ SafeRelease(dxgiAdapter2); +++ +++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject(mDxgiAdapter); +++ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr); +++ SafeRelease(dxgiAdapter3); ++ } ++ ++ gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig( ++@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions ++ ++ // All D3D feature levels support robust resource init ++ outExtensions->robustResourceInitialization = true; +++ +++ // color space selection supported in DXGI 1.4 only +++ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4; +++ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4; +++ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4; ++ } ++ ++ gl::Error Renderer11::flush() ++@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow, ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples) +++ EGLint samples, +++ EGLint colorSpace) ++ { ++ return new SwapChain11(this, GetAs(nativeWindow), shareHandle, d3dTexture, ++- backBufferFormat, depthBufferFormat, orientation, samples); +++ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace); ++ } ++ ++ void *Renderer11::getD3DDevice() ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h ++index a8c24e681b..3516bf779d 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h ++@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps ++ ++ D3D_FEATURE_LEVEL featureLevel; ++ bool supportsDXGI1_2; // Support for DXGI 1.2 +++ bool supportsDXGI1_4; // Support for DXGI 1.4 ++ bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView ++ bool supportsConstantBufferOffsets; // Support for Constant buffer offset ++ bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader ++@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples) override; +++ EGLint samples, +++ EGLint colorSpace) override; ++ egl::Error getD3DTextureInfo(const egl::Config *configuration, ++ IUnknown *d3dTexture, ++ EGLint *width, ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp ++index dcfd06484d..fc967b90d0 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp ++@@ -18,6 +18,11 @@ ++ #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h" ++ #include "third_party/trace_event/trace_event.h" ++ +++#if 0 +++// used only for HDR metadata configuration options +++#include +++#endif +++ ++ // Precompiled shaders ++ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h" ++ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h" ++@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer, ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples) +++ EGLint samples, +++ EGLint colorSpace) ++ : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat), ++ mRenderer(renderer), ++ mWidth(-1), ++ mHeight(-1), ++ mOrientation(orientation), +++ mColorSpace(colorSpace), ++ mAppCreatedShareHandle(mShareHandle != nullptr), ++ mSwapInterval(0), ++ mPassThroughResourcesInit(false), ++@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context, ++ mSwapChain1 = d3d11::DynamicCastComObject(mSwapChain); ++ } ++ +++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4) +++ { +++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject(mSwapChain); +++ +++ printf("*** EGL colorSpace: 0x%X\n", mColorSpace); +++ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat); +++ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat()); +++ +++ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) { +++ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; +++ switch (mColorSpace) +++ { +++ case EGL_GL_COLORSPACE_SRGB_KHR: +++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; +++ break; +++ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: +++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; +++ break; +++ case EGL_GL_COLORSPACE_BT2020_PQ_EXT: +++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; +++ break; +++ default: +++ ASSERT(0 && "Unsupported colorspace requested"); +++ } +++ +++ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace); +++ +++ UINT supported = 0; +++ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported); +++ ASSERT(SUCCEEDED(result)); +++ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { +++ SafeRelease(swapChain3); +++ return EGL_BAD_MATCH; +++ } +++ +++ result = swapChain3->SetColorSpace1(nativeColorSpace); +++ ASSERT(SUCCEEDED(result)); +++ } +++ +++ SafeRelease(swapChain3); +++ +++#if 0 +++ +++ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject(mSwapChain); +++ +++ DXGI_HDR_METADATA_HDR10 md; +++ md.RedPrimary[0] = 0.680 * 50000; +++ md.RedPrimary[1] = 0.320 * 50000; +++ md.GreenPrimary[0] = 0.265 * 50000; +++ md.GreenPrimary[1] = 0.690 * 50000; +++ md.BluePrimary[0] = 0.150 * 50000; +++ md.BluePrimary[1] = 0.060 * 50000; +++ md.WhitePoint[0] = 0.3127 * 50000; +++ md.WhitePoint[1] = 0.3290 * 50000; +++ md.MaxMasteringLuminance = 1000 * 10000; +++ md.MinMasteringLuminance = 0.001 * 10000; +++ md.MaxContentLightLevel = 1000; +++ md.MaxFrameAverageLightLevel = 200; +++ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md); +++ printf("*** Result hdr 0x%X\n", result); +++ SafeRelease(swapChain4); +++#endif +++ } +++ ++ ID3D11Texture2D *backbufferTex = nullptr; ++ result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), ++ reinterpret_cast(&backbufferTex)); ++ ASSERT(SUCCEEDED(result)); +++ +++ // TODO: recover rendering to sRGB +++ // +++ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc; +++ // offscreenRTVDesc.Format = getSwapChainNativeFormat(); +++ // +++ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) { +++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) { +++ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; +++ // } +++ // +++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) { +++ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; +++ // } +++ // } +++ // +++ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format); +++ ++ const auto &format = ++ d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps()); ++ mBackBufferTexture.set(backbufferTex, format); ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h ++index eca068210b..2a4b9ba274 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h ++@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples); +++ EGLint samples, +++ EGLint colorSpace); ++ ~SwapChain11() override; ++ ++ EGLint resize(const gl::Context *context, ++@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D ++ EGLint mWidth; ++ EGLint mHeight; ++ const EGLint mOrientation; +++ EGLint mColorSpace; ++ bool mAppCreatedShareHandle; ++ unsigned int mSwapInterval; ++ bool mPassThroughResourcesInit; ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp ++index 5394e3d3e7..af52c41d00 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp ++@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, ++ ++ // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a ++ // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain. +++ // +++ // NOTE: in non-flip mode HDR rendering is not supported, so use it +++ // by default ++ IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject(factory); ++ if (factory2 != nullptr) ++ { ++@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, ++ swapChainDesc.SampleDesc.Quality = 0; ++ swapChainDesc.BufferUsage = ++ DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER; ++- swapChainDesc.BufferCount = 1; +++ swapChainDesc.BufferCount = 2; ++ swapChainDesc.Scaling = DXGI_SCALING_STRETCH; ++- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; +++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; ++ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; ++ swapChainDesc.Flags = 0; ++ IDXGISwapChain1 *swapChain1 = nullptr; ++@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, ++ } ++ ++ DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; ++- swapChainDesc.BufferCount = 1; +++ swapChainDesc.BufferCount = 2; ++ swapChainDesc.BufferDesc.Format = format; ++ swapChainDesc.BufferDesc.Width = width; ++ swapChainDesc.BufferDesc.Height = height; ++@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, ++ swapChainDesc.SampleDesc.Count = samples; ++ swapChainDesc.SampleDesc.Quality = 0; ++ swapChainDesc.Windowed = TRUE; +++ +++ /** +++ * NOTE1: in discard mode the swap chain doesn't support partial +++ * presentatiopn with Present1() call. Though it is not a big +++ * problem, because in case DXGI 1.2 is supported this code is +++ * unreachable. +++ * +++ * NOTE2: Flip modes are not supported on Windows 7 and the like, +++ * so use a legacy mode as a fallback +++ */ ++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; ++ ++ HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain); ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp ++index 75c6298868..58596169a8 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp ++@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow, ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples) +++ EGLint samples, +++ EGLint colorSpace) ++ { +++ UNUSED_VARIABLE(colorSpace); ++ return new SwapChain9(this, GetAs(nativeWindow), shareHandle, d3dTexture, ++ backBufferFormat, depthBufferFormat, orientation); ++ } ++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h ++index 9ddee45f0f..ce4bb201e5 100644 ++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h +++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h ++@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D ++ GLenum backBufferFormat, ++ GLenum depthBufferFormat, ++ EGLint orientation, ++- EGLint samples) override; +++ EGLint samples, +++ EGLint colorSpace) override; ++ egl::Error getD3DTextureInfo(const egl::Config *configuration, ++ IUnknown *d3dTexture, ++ EGLint *width, ++diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp ++index 13a3a9e280..858d7ee929 100644 ++--- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp +++++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp ++@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin ++ "either EGL_TRUE or EGL_FALSE."; ++ } ++ break; +++ case EGL_GL_COLORSPACE: +++ +++ if (!displayExtensions.colorspaceSRGB) +++ { +++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; +++ } +++ +++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) +++ { +++ if (!displayExtensions.colorspaceSCRGBLinear) +++ { +++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; +++ } +++ } +++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) +++ { +++ if (!displayExtensions.colorspaceBt2020PQ) +++ { +++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; +++ } +++ } +++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) +++ { +++ return EglBadAttribute() << "Unknown EGL color space requested"; +++ } +++ break; ++ ++ default: ++ return EglBadAttribute(); ++@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri ++ } ++ break; ++ +++ case EGL_GL_COLORSPACE: +++ +++ if (!displayExtensions.colorspaceSRGB) +++ { +++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; +++ } +++ +++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) +++ { +++ if (!displayExtensions.colorspaceSCRGBLinear) +++ { +++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; +++ } +++ } +++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) +++ { +++ if (!displayExtensions.colorspaceBt2020PQ) +++ { +++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; +++ } +++ } +++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) +++ { +++ return EglBadAttribute() << "Unknown EGL color space requested"; +++ } +++ break; +++ ++ default: ++ return EglBadAttribute(); ++ } ++-- ++2.20.1.windows.1 ++ +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch b/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch deleted file mode 100644 index 79b53123af..0000000000 --- a/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 50ec48b6622f07550d71c694d037131c7b0a8c7b Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Mon, 11 Feb 2019 18:07:20 +0300 -Subject: [PATCH 03/27] Fix debug on openGL ES - -Change-Id: I08d1adf87b305c380a0f2177edf4ff9de109e4d6 ---- - src/gui/opengl/qopengldebug.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/gui/opengl/qopengldebug.cpp b/src/gui/opengl/qopengldebug.cpp -index 2e628a2b..9f1bb768 100644 ---- a/src/gui/opengl/qopengldebug.cpp -+++ b/src/gui/opengl/qopengldebug.cpp -@@ -1366,7 +1366,7 @@ bool QOpenGLDebugLogger::initialize() - - #define GET_DEBUG_PROC_ADDRESS(procName) \ - d->procName = reinterpret_cast< qt_ ## procName ## _t >( \ -- d->context->getProcAddress(#procName) \ -+ d->context->getProcAddress(d->context->isOpenGLES() ? (#procName "KHR") : (#procName)) \ - ); - - GET_DEBUG_PROC_ADDRESS(glDebugMessageControl); --- -2.22.0.windows.1 - diff --git a/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch b/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch new file mode 100644 index 0000000000..303174c533 --- /dev/null +++ b/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch @@ -0,0 +1,276 @@ +From 806fae9657a13559332796ade9cf1b302d014e46 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 13 Feb 2019 16:56:11 +0300 +Subject: [PATCH 04/17] Implement color space selection for QSurfaceFormat + +With the patch one can select color space of openGL surface +which is used a a root surface of the underlying native window. + +This feature is needed, e.g. when the user wants to render HDR +content on screen. In such a case OS should be instructed about +how to treat the graphical data in the application framebuffer. + +The easiest approach is to call QSurfaceFormat::setDefaultFormat() +before creating the first application window. In such a case the +root surface will (may) be in the requested format. + +Supported color spaces/formats: + +1) sRGB, SDR +2) scRGB (Rec 709, gamma 1.0), HDR +3) Rec 2020 PQ, HDR + +Please take into account that in real life the user should select +proper bit depth for each color space, otherwise the system will +refuse to create the surface: + +1) sRGB --- 8 bit or 10 bit +2) scRGB --- 16 bit only +3) Rec 2020 PQ --- 10 bit only + +Please note that color space selection is supported only on +platforms with DXGI 1.4 and higher. + +Change-Id: I5f4945db9798d542f19c8ff1af1effa34f7745fd +--- + src/gui/kernel/qsurfaceformat.cpp | 11 ++++ + src/gui/kernel/qsurfaceformat.h | 4 +- + src/gui/opengl/qopenglframebufferobject.cpp | 7 ++- + .../platforms/windows/qwindowseglcontext.cpp | 57 +++++++++++++++++-- + .../platforms/windows/qwindowseglcontext.h | 6 +- + .../platforms/windows/qwindowsopenglcontext.h | 2 +- + .../platforms/windows/qwindowswindow.cpp | 8 ++- + 7 files changed, 83 insertions(+), 12 deletions(-) + +diff --git a/src/gui/kernel/qsurfaceformat.cpp b/src/gui/kernel/qsurfaceformat.cpp +index 1a814ec21f..fc8b9c4f43 100644 +--- a/src/gui/kernel/qsurfaceformat.cpp ++++ b/src/gui/kernel/qsurfaceformat.cpp +@@ -221,6 +221,17 @@ public: + set, the window will be created with an sRGB-capable default + framebuffer. Note that some platforms may return windows with a sRGB-capable + default framebuffer even when not requested explicitly. ++ ++ \value scRGBColorSpace When \c{EGL_EXT_gl_colorspace_scrgb_linear} ++ is supported by the platform and this value is set, the window will ++ be created with an scRGB-capable default framebuffer. Note that some ++ platforms may return windows with a scRGB-capable default framebuffer ++ even when not requested explicitly. It usually happens when the application ++ requests 16-bit surface format. ++ ++ \value bt2020PQColorSpace When \c{EGL_EXT_gl_colorspace_bt2020_pq} ++ is supported by the platform and this value is set, the window will ++ be created with an bt2020 PQ default framebuffer. + */ + + /*! +diff --git a/src/gui/kernel/qsurfaceformat.h b/src/gui/kernel/qsurfaceformat.h +index ed63eb8bbf..9ba6a29b7a 100644 +--- a/src/gui/kernel/qsurfaceformat.h ++++ b/src/gui/kernel/qsurfaceformat.h +@@ -87,7 +87,9 @@ public: + + enum ColorSpace { + DefaultColorSpace, +- sRGBColorSpace ++ sRGBColorSpace, ++ scRGBColorSpace, ++ bt2020PQColorSpace + }; + Q_ENUM(ColorSpace) + +diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp +index cae3d516c4..ccdccb637a 100644 +--- a/src/gui/opengl/qopenglframebufferobject.cpp ++++ b/src/gui/opengl/qopenglframebufferobject.cpp +@@ -545,10 +545,13 @@ void QOpenGLFramebufferObjectPrivate::initTexture(int idx) + ColorAttachment &color(colorAttachments[idx]); + + GLuint pixelType = GL_UNSIGNED_BYTE; +- if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) ++ if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) { + pixelType = GL_UNSIGNED_INT_2_10_10_10_REV; +- else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) ++ } else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) { + pixelType = GL_UNSIGNED_SHORT; ++ } else if (color.internalFormat == GL_RGBA16F) { ++ pixelType = GL_HALF_FLOAT; ++ } + + funcs.glTexImage2D(target, 0, color.internalFormat, color.size.width(), color.size.height(), 0, + GL_RGBA, pixelType, NULL); +diff --git a/src/plugins/platforms/windows/qwindowseglcontext.cpp b/src/plugins/platforms/windows/qwindowseglcontext.cpp +index 063e81150e..4cd745eac6 100644 +--- a/src/plugins/platforms/windows/qwindowseglcontext.cpp ++++ b/src/plugins/platforms/windows/qwindowseglcontext.cpp +@@ -151,8 +151,9 @@ bool QWindowsLibEGL::init() + eglGetCurrentDisplay = RESOLVE((EGLDisplay (EGLAPIENTRY *)(void)), eglGetCurrentDisplay); + eglSwapBuffers = RESOLVE((EGLBoolean (EGLAPIENTRY *)(EGLDisplay , EGLSurface)), eglSwapBuffers); + eglGetProcAddress = RESOLVE((QFunctionPointer (EGLAPIENTRY * )(const char *)), eglGetProcAddress); ++ eglQueryString = RESOLVE((const char* (EGLAPIENTRY *)(EGLDisplay, EGLint)), eglQueryString); + +- if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress) ++ if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress || !eglQueryString) + return false; + + eglGetPlatformDisplayEXT = nullptr; +@@ -197,8 +198,15 @@ bool QWindowsLibGLESv2::init() + } + + QWindowsEGLStaticContext::QWindowsEGLStaticContext(EGLDisplay display) +- : m_display(display) ++ : m_display(display), ++ m_hasSRGBColorSpaceSupport(false), ++ m_hasSCRGBColorSpaceSupport(false), ++ m_hasBt2020PQColorSpaceSupport(false) + { ++ const char *eglExtensions = libEGL.eglQueryString(display, EGL_EXTENSIONS); ++ m_hasSRGBColorSpaceSupport = strstr(eglExtensions, "EGL_KHR_gl_colorspace") != nullptr; ++ m_hasSCRGBColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_scrgb_linear") != nullptr; ++ m_hasBt2020PQColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_bt2020_pq") != nullptr; + } + + bool QWindowsEGLStaticContext::initializeAngle(QWindowsOpenGLTester::Renderers preferredType, HDC dc, +@@ -297,11 +305,48 @@ QWindowsOpenGLContext *QWindowsEGLStaticContext::createContext(QOpenGLContext *c + return new QWindowsEGLContext(this, context->format(), context->shareHandle()); + } + +-void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) ++void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, ++ QSurfaceFormat::ColorSpace colorSpace, int *err) + { + *err = 0; ++ ++ EGLint eglColorSpace = EGL_GL_COLORSPACE_LINEAR_KHR; ++ bool colorSpaceSupported = false; ++ ++ switch (colorSpace) { ++ case QSurfaceFormat::DefaultColorSpace: ++ colorSpaceSupported = m_hasSRGBColorSpaceSupport; ++ break; ++ case QSurfaceFormat::sRGBColorSpace: ++ eglColorSpace = EGL_GL_COLORSPACE_SRGB_KHR; ++ colorSpaceSupported = m_hasSRGBColorSpaceSupport; ++ break; ++ case QSurfaceFormat::scRGBColorSpace: ++ eglColorSpace = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT; ++ colorSpaceSupported = m_hasSCRGBColorSpaceSupport; ++ break; ++ case QSurfaceFormat::bt2020PQColorSpace: ++ eglColorSpace = EGL_GL_COLORSPACE_BT2020_PQ_EXT; ++ colorSpaceSupported = m_hasBt2020PQColorSpaceSupport; ++ break; ++ } ++ ++ QVector attributes; ++ ++ if (colorSpaceSupported) { ++ attributes << EGL_GL_COLORSPACE << eglColorSpace; ++ } ++ ++ attributes << EGL_NONE; ++ ++ if (!colorSpaceSupported && colorSpace != QSurfaceFormat::DefaultColorSpace) { ++ qWarning().nospace() << __FUNCTION__ << ": Requested color space is not supported by EGL implementation: " << colorSpace << " (egl: 0x" << hex << eglColorSpace << ")"; ++ } ++ ++ + EGLSurface surface = libEGL.eglCreateWindowSurface(m_display, nativeConfig, +- static_cast(nativeWindow), nullptr); ++ static_cast(nativeWindow), ++ attributes.constData()); + if (surface == EGL_NO_SURFACE) { + *err = libEGL.eglGetError(); + qWarning("%s: Could not create the EGL window surface: 0x%x", __FUNCTION__, *err); +@@ -349,6 +394,7 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG + format.setSamples(sampleCount); + format.setStereo(false); + format.setSwapInterval(referenceFormat.swapInterval()); ++ format.setColorSpace(referenceFormat.colorSpace()); + + // Clear the EGL error state because some of the above may + // have errored out because the attribute is not applicable +@@ -378,7 +424,6 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG + \internal + \ingroup qt-lighthouse-win + */ +- + QWindowsEGLContext::QWindowsEGLContext(QWindowsEGLStaticContext *staticContext, + const QSurfaceFormat &format, + QPlatformOpenGLContext *share) +@@ -483,6 +528,8 @@ bool QWindowsEGLContext::makeCurrent(QPlatformSurface *surface) + // Simulate context loss as the context is useless. + QWindowsEGLStaticContext::libEGL.eglDestroyContext(m_eglDisplay, m_eglContext); + m_eglContext = EGL_NO_CONTEXT; ++ } else if (err == EGL_BAD_MATCH) { ++ qCDebug(lcQpaGl) << "Got bad match in createWindowSurface() for context" << this << "Check color space configuration."; + } + return false; + } +diff --git a/src/plugins/platforms/windows/qwindowseglcontext.h b/src/plugins/platforms/windows/qwindowseglcontext.h +index 8a1e1ddae8..9f7742e6fb 100644 +--- a/src/plugins/platforms/windows/qwindowseglcontext.h ++++ b/src/plugins/platforms/windows/qwindowseglcontext.h +@@ -80,6 +80,7 @@ struct QWindowsLibEGL + QFunctionPointer (EGLAPIENTRY *eglGetProcAddress)(const char *procname); + + EGLDisplay (EGLAPIENTRY * eglGetPlatformDisplayEXT)(EGLenum platform, void *native_display, const EGLint *attrib_list); ++ const char* (EGLAPIENTRY * eglQueryString)(EGLDisplay dpy, EGLint name); + + private: + #if !defined(QT_STATIC) || defined(QT_OPENGL_DYNAMIC) +@@ -121,7 +122,7 @@ public: + void *moduleHandle() const override { return libGLESv2.moduleHandle(); } + QOpenGLContext::OpenGLModuleType moduleType() const override { return QOpenGLContext::LibGLES; } + +- void *createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) override; ++ void *createWindowSurface(void *nativeWindow, void *nativeConfig, QSurfaceFormat::ColorSpace colorSpace, int *err) override; + void destroyWindowSurface(void *nativeSurface) override; + + QSurfaceFormat formatFromConfig(EGLDisplay display, EGLConfig config, const QSurfaceFormat &referenceFormat); +@@ -135,6 +136,9 @@ private: + EGLDisplay *display, EGLint *major, EGLint *minor); + + const EGLDisplay m_display; ++ bool m_hasSRGBColorSpaceSupport; ++ bool m_hasSCRGBColorSpaceSupport; ++ bool m_hasBt2020PQColorSpaceSupport; + }; + + class QWindowsEGLContext : public QWindowsOpenGLContext +diff --git a/src/plugins/platforms/windows/qwindowsopenglcontext.h b/src/plugins/platforms/windows/qwindowsopenglcontext.h +index cc6d93d35e..61c0e28767 100644 +--- a/src/plugins/platforms/windows/qwindowsopenglcontext.h ++++ b/src/plugins/platforms/windows/qwindowsopenglcontext.h +@@ -63,7 +63,7 @@ public: + + // If the windowing system interface needs explicitly created window surfaces (like EGL), + // reimplement these. +- virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, int * /*err*/) { return 0; } ++ virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, QSurfaceFormat::ColorSpace /*colorSpace*/, int * /*err*/) { return 0; } + virtual void destroyWindowSurface(void * /*nativeSurface*/) { } + + protected: +diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp +index 3bf424d0ac..7597fcb64d 100644 +--- a/src/plugins/platforms/windows/qwindowswindow.cpp ++++ b/src/plugins/platforms/windows/qwindowswindow.cpp +@@ -2874,9 +2874,13 @@ void *QWindowsWindow::surface(void *nativeConfig, int *err) + return 0; + #endif + #ifndef QT_NO_OPENGL ++ ++ ++ + if (!m_surface) { +- if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) +- m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, err); ++ if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { ++ m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, m_format.colorSpace(), err); ++ } + } + + return m_surface; +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0005-Implement-color-conversion-for-the-backing-store-tex.patch b/3rdparty/ext_qt/0005-Implement-color-conversion-for-the-backing-store-tex.patch new file mode 100644 index 0000000000..cd0dbb0bf4 --- /dev/null +++ b/3rdparty/ext_qt/0005-Implement-color-conversion-for-the-backing-store-tex.patch @@ -0,0 +1,627 @@ +From d9e2e0e62952dfcae6922307555e08ef5bce0c84 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Thu, 22 Nov 2018 15:47:48 +0300 +Subject: [PATCH 05/17] Implement color conversion for the backing store + texture + +If the window surface is not in sRGB mode, then the backing store +surface should be converted into the destinations color space. + +This patch adds the most popular color space transitions into +QOpenGLTextureBlitter. + +The patch also implements QOpenGLWidget::setTextureColorSpace(), +which notifies the compositor about the color space of non-native +openGL widgets, so that the data could be converted correctly. + +TODO: should we implement the same for QOpenGLWindow:: + setTextureColorSpace()? + +Note: +The channels should be swizzled into RGBA *before* applying the +color space conversion matrix. Otherwise the colors will be skewed, +because the conversion function for R and B channels is not the same. + +Change-Id: Icbf599952c93cc04de417d0c3790a65282741655 +--- + src/gui/opengl/qopengltextureblitter.cpp | 222 ++++++++++++++++-- + src/gui/opengl/qopengltextureblitter.h | 12 +- + src/gui/painting/qplatformbackingstore.cpp | 16 +- + src/gui/painting/qplatformbackingstore.h | 4 +- + .../qopenglcompositorbackingstore.cpp | 2 +- + src/widgets/kernel/qopenglwidget.cpp | 45 +++- + src/widgets/kernel/qopenglwidget.h | 3 + + src/widgets/kernel/qwidget_p.h | 1 + + src/widgets/kernel/qwidgetbackingstore.cpp | 2 +- + 9 files changed, 281 insertions(+), 26 deletions(-) + +diff --git a/src/gui/opengl/qopengltextureblitter.cpp b/src/gui/opengl/qopengltextureblitter.cpp +index b65df9dc82..5f6dbff292 100644 +--- a/src/gui/opengl/qopengltextureblitter.cpp ++++ b/src/gui/opengl/qopengltextureblitter.cpp +@@ -131,14 +131,85 @@ static const char vertex_shader[] = + "}"; + + static const char fragment_shader[] = +- "varying highp vec2 uv;" +- "uniform sampler2D textureSampler;" +- "uniform bool swizzle;" +- "uniform highp float opacity;" ++ "varying highp vec2 uv;\n" ++ "uniform sampler2D textureSampler;\n" ++ "uniform bool swizzle;\n" ++ "uniform highp float opacity;\n" ++ "#if defined SCRGB_TO_SRGB\n" ++ "highp vec4 linearToSRGB(highp vec4 value)\n" ++ "{\n" ++ " bvec4 cutoff = lessThan(value, vec4(0.0031308));\n" ++ " const highp vec2 a1 = vec2(0.055, 0.0);\n" ++ " const highp vec2 c2 = vec2(1.055, 1.0);\n" ++ " const highp vec2 m3 = vec2(2.4, 1.0);\n" ++ " const highp vec2 c4 = vec2(12.92, 1.0);\n" ++ " highp vec4 higher = c2.xxxy * pow(value, 1.0 / m3.xxxy) - a1.xxxy;\n" ++ " highp vec4 lower = value * c4.xxxy;\n" ++ " return mix(higher, lower, vec4(cutoff));\n" ++ "}\n" ++ "#endif\n" ++ "#if defined SRGB_TO_SCRGB || defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n" ++ "highp vec4 sRgbToLinear(highp vec4 sRGB)\n" ++ "{\n" ++ " bvec4 cutoff = lessThan(sRGB, vec4(0.04045));\n" ++ " const highp vec2 a1 = vec2(0.055, 0.0);\n" ++ " const highp vec2 c2 = vec2(1.055, 1.0);\n" ++ " const highp vec2 m3 = vec2(2.4, 1.0);\n" ++ " const highp vec2 c4 = vec2(12.92, 1.0);\n" ++ " highp vec4 higher = pow((sRGB + a1.xxxy) / c2.xxxy, m3.xxxy);\n" ++ " highp vec4 lower = sRGB / c4.xxxy;\n" ++ " return mix(higher, lower, vec4(cutoff));\n" ++ "}\n" ++ "#endif\n" ++ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n" ++ "highp vec4 applySmpte2084Curve(highp vec4 L)\n" ++ "{" ++ " const highp vec2 m1 = vec2(2610.0 / 4096.0 / 4.0, 1.0);\n" ++ " const highp vec2 m2 = vec2(2523.0 / 4096.0 * 128.0, 1.0);\n" ++ " const highp vec2 a1 = vec2(3424.0 / 4096.0, 0.0);\n" ++ " const highp vec2 c2 = vec2(2413.0 / 4096.0 * 32.0, 1.0);\n" ++ " const highp vec2 c3 = vec2(2392.0 / 4096.0 * 32.0, 1.0);\n" ++ " const highp vec2 a4 = vec2(1.0, 0.0);\n" ++ " highp vec4 Lp = pow(L, m1.xxxy);\n" ++ " highp vec4 res = pow((a1.xxxy + c2.xxxy * Lp) / (a4.xxxy + c3.xxxy * Lp), m2.xxxy);\n" ++ " return res;" ++ "}\n" ++ "#endif\n" ++ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n" ++ "highp vec4 scRgbToBt2020pq(highp vec4 value)\n" ++ "{\n" ++ " const highp mat4 convMat = " ++ " mat4(0.627402, 0.069095, 0.016394, 0.0," ++ " 0.329292, 0.919544, 0.088028, 0.0," ++ " 0.043306, 0.011360, 0.895578, 0.0," ++ " 0.0, 0.0, 0.0, 1.0);" ++ "" ++ " value = convMat * value;\n" ++ " return applySmpte2084Curve(0.008 * value);" ++ "}\n" ++ "#endif\n" ++ "#if defined SRGB_TO_BT2020PQ\n" ++ "highp vec4 sRgbToBt2020pq(highp vec4 value)\n" ++ "{\n" ++ " value = sRgbToLinear(value);" ++ " return scRgbToBt2020pq(value);" ++ "}\n" ++ "#endif\n" ++ "\n" + "void main() {" + " highp vec4 tmpFragColor = texture2D(textureSampler,uv);" +- " tmpFragColor.a *= opacity;" +- " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;" ++ " tmpFragColor.a *= opacity;\n" ++ " tmpFragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;\n" ++ "#if defined SRGB_TO_SCRGB\n" ++ " tmpFragColor = sRgbToLinear(tmpFragColor);\n" ++ "#elif defined SRGB_TO_BT2020PQ\n" ++ " tmpFragColor = sRgbToBt2020pq(tmpFragColor);\n" ++ "#elif defined SCRGB_TO_BT2020PQ\n" ++ " tmpFragColor = scRgbToBt2020pq(tmpFragColor);\n" ++ "#elif defined SCRGB_TO_SRGB\n" ++ " tmpFragColor = linearToSRGB(tmpFragColor);\n" ++ "#endif\n" ++ " gl_FragColor = tmpFragColor;" + "}"; + + static const char fragment_shader_external_oes[] = +@@ -187,6 +258,23 @@ private: + GLenum m_target; + }; + ++class ColorSpaceConversion : public QPair ++{ ++public: ++ ColorSpaceConversion() { }; ++ ColorSpaceConversion(QSurfaceFormat::ColorSpace srcColorSpace, ++ QSurfaceFormat::ColorSpace dstColorSpace) ++ : QPair(srcColorSpace, dstColorSpace) ++ { } ++ ++ QSurfaceFormat::ColorSpace source() const { ++ return first; ++ } ++ QSurfaceFormat::ColorSpace destination() const { ++ return second; ++ } ++}; ++ + class QOpenGLTextureBlitterPrivate + { + public: +@@ -197,16 +285,29 @@ public: + }; + + enum ProgramIndex { +- TEXTURE_2D, +- TEXTURE_EXTERNAL_OES ++ TEXTURE_2D = 0, ++ TEXTURE_2D_SRGB_TO_SCRGB, ++ TEXTURE_2D_SCRGB_TO_SRGB, ++ TEXTURE_2D_SRGB_TO_BT2020PQ, ++ TEXTURE_2D_SCRGB_TO_BT2020PQ, ++ TEXTURE_EXTERNAL_OES, ++ ++ PROGRAM_COUNT + }; + + QOpenGLTextureBlitterPrivate() : + swizzle(false), + opacity(1.0f), + vao(new QOpenGLVertexArrayObject), +- currentTarget(TEXTURE_2D) +- { } ++ currentTarget(GL_NONE), ++ colorSpaceConversion(0) ++ { ++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::DefaultColorSpace, QSurfaceFormat::DefaultColorSpace); ++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::scRGBColorSpace); ++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::sRGBColorSpace); ++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace); ++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace); ++ } + + bool buildProgram(ProgramIndex idx, const char *vs, const char *fs); + +@@ -214,6 +315,7 @@ public: + void blit(GLuint texture, const QMatrix4x4 &vertexTransform, QOpenGLTextureBlitter::Origin origin); + + void prepareProgram(const QMatrix4x4 &vertexTransform); ++ int calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace); + + QOpenGLBuffer vertexBuffer; + QOpenGLBuffer textureBuffer; +@@ -239,18 +341,48 @@ public: + bool swizzle; + float opacity; + TextureMatrixUniform textureMatrixUniformState; +- } programs[2]; ++ } programs[PROGRAM_COUNT]; + bool swizzle; + float opacity; + QScopedPointer vao; + GLenum currentTarget; ++ ++ int colorSpaceConversion; ++ QVector supportedColorSpaceConversions; + }; + +-static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target) ++int QOpenGLTextureBlitterPrivate::calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace) ++{ ++ // TODO: auto-detect destination color space of the surface ++ // in case of default color space ++ ++ // disable color management if at least one of the color ++ // spaces is declared as default ++ if (srcColorSpace == QSurfaceFormat::DefaultColorSpace || ++ dstColorSpace == QSurfaceFormat::DefaultColorSpace) { ++ ++ return 0; ++ } ++ ++ // disable color management if source and destination color ++ // spaces are the same ++ if (srcColorSpace == dstColorSpace) { ++ return 0; ++ } ++ ++ ColorSpaceConversion conversion(srcColorSpace, dstColorSpace); ++ return supportedColorSpaceConversions.indexOf(conversion); ++} ++ ++static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target, int colorSpaceConversion) + { + switch (target) { +- case GL_TEXTURE_2D: +- return QOpenGLTextureBlitterPrivate::TEXTURE_2D; ++ case GL_TEXTURE_2D: { ++ QOpenGLTextureBlitterPrivate::ProgramIndex index = ++ QOpenGLTextureBlitterPrivate::ProgramIndex( ++ int(QOpenGLTextureBlitterPrivate::TEXTURE_2D) + colorSpaceConversion); ++ return index; ++ } + case GL_TEXTURE_EXTERNAL_OES: + return QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES; + default: +@@ -261,7 +393,7 @@ static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GL + + void QOpenGLTextureBlitterPrivate::prepareProgram(const QMatrix4x4 &vertexTransform) + { +- Program *program = &programs[targetToProgramIndex(currentTarget)]; ++ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)]; + + vertexBuffer.bind(); + program->glProgram->setAttributeBuffer(program->vertexCoordAttribPos, GL_FLOAT, 0, 3, 0); +@@ -293,7 +425,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture, + TextureBinder binder(currentTarget, texture); + prepareProgram(vertexTransform); + +- Program *program = &programs[targetToProgramIndex(currentTarget)]; ++ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)]; + program->glProgram->setUniformValue(program->textureTransformUniformPos, textureTransform); + program->textureMatrixUniformState = User; + +@@ -307,7 +439,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture, + TextureBinder binder(currentTarget, texture); + prepareProgram(vertexTransform); + +- Program *program = &programs[targetToProgramIndex(currentTarget)]; ++ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)]; + if (origin == QOpenGLTextureBlitter::OriginTopLeft) { + if (program->textureMatrixUniformState != IdentityFlipped) { + QMatrix3x3 flipped; +@@ -408,6 +540,28 @@ bool QOpenGLTextureBlitter::create() + } else { + if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D, vertex_shader, fragment_shader)) + return false; ++ ++ // TODO: create non-default transformations on-demand ++ { ++ const QString shader = QString("#define SRGB_TO_SCRGB\n %1").arg(fragment_shader); ++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB, vertex_shader, shader.toLatin1().constData())) ++ return false; ++ } ++ { ++ const QString shader = QString("#define SCRGB_TO_SRGB\n %1").arg(fragment_shader); ++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_SRGB, vertex_shader, shader.toLatin1().constData())) ++ return false; ++ } ++ { ++ const QString shader = QString("#define SRGB_TO_BT2020PQ\n %1").arg(fragment_shader); ++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData())) ++ return false; ++ } ++ { ++ const QString shader = QString("#define SCRGB_TO_BT2020PQ\n %1").arg(fragment_shader); ++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData())) ++ return false; ++ } + if (supportsExternalOESTarget()) + if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES, vertex_shader, fragment_shader_external_oes)) + return false; +@@ -455,6 +609,8 @@ void QOpenGLTextureBlitter::destroy() + return; + Q_D(QOpenGLTextureBlitter); + d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram.reset(); ++ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB].glProgram.reset(); ++ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ].glProgram.reset(); + d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES].glProgram.reset(); + d->vertexBuffer.destroy(); + d->textureBuffer.destroy(); +@@ -484,15 +640,26 @@ bool QOpenGLTextureBlitter::supportsExternalOESTarget() const + + \sa release(), blit() + */ +-void QOpenGLTextureBlitter::bind(GLenum target) ++void QOpenGLTextureBlitter::bind(GLenum target, ++ QSurfaceFormat::ColorSpace srcColorSpace, ++ QSurfaceFormat::ColorSpace dstColorSpace) + { + Q_D(QOpenGLTextureBlitter); + + if (d->vao->isCreated()) + d->vao->bind(); + ++ const int index = d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace); ++ ++ if (index >= 0) { ++ d->colorSpaceConversion = index; ++ } else { ++ qWarning() << "QOpenGLTextureBlitter::bind(): color space conversion is not supported" << srcColorSpace << dstColorSpace; ++ d->colorSpaceConversion = 0; // noop conversion ++ } ++ + d->currentTarget = target; +- QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target)]; ++ QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target, d->colorSpaceConversion)]; + p->glProgram->bind(); + + d->vertexBuffer.bind(); +@@ -506,6 +673,21 @@ void QOpenGLTextureBlitter::bind(GLenum target) + d->textureBuffer.release(); + } + ++void QOpenGLTextureBlitter::rebind(GLenum target, QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace) ++{ ++ Q_D(QOpenGLTextureBlitter); ++ ++ if (d->vao->isCreated() && ++ d->currentTarget == target && ++ d->colorSpaceConversion == d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace)) { ++ ++ // the blitter is already configured in the correct state, so just skip it ++ return; ++ } ++ ++ bind(target, srcColorSpace, dstColorSpace); ++} ++ + /*! + Unbinds the graphics resources used by the blitter. + +@@ -514,7 +696,7 @@ void QOpenGLTextureBlitter::bind(GLenum target) + void QOpenGLTextureBlitter::release() + { + Q_D(QOpenGLTextureBlitter); +- d->programs[targetToProgramIndex(d->currentTarget)].glProgram->release(); ++ d->programs[targetToProgramIndex(d->currentTarget, d->colorSpaceConversion)].glProgram->release(); + if (d->vao->isCreated()) + d->vao->release(); + } +diff --git a/src/gui/opengl/qopengltextureblitter.h b/src/gui/opengl/qopengltextureblitter.h +index 2f7c6b1a0a..3c87e4e2b5 100644 +--- a/src/gui/opengl/qopengltextureblitter.h ++++ b/src/gui/opengl/qopengltextureblitter.h +@@ -48,6 +48,9 @@ + #include + #include + ++// TODO: less includes!!! ++#include ++ + QT_BEGIN_NAMESPACE + + class QOpenGLTextureBlitterPrivate; +@@ -69,7 +72,14 @@ public: + + bool supportsExternalOESTarget() const; + +- void bind(GLenum target = GL_TEXTURE_2D); ++ void bind(GLenum target = GL_TEXTURE_2D, ++ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace, ++ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace); ++ ++ void rebind(GLenum target = GL_TEXTURE_2D, ++ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace, ++ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace); ++ + void release(); + + void setRedBlueSwizzle(bool swizzle); +diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp +index c71d82546a..8dd96a66bd 100644 +--- a/src/gui/painting/qplatformbackingstore.cpp ++++ b/src/gui/painting/qplatformbackingstore.cpp +@@ -132,6 +132,7 @@ struct QBackingstoreTextureInfo + QRect rect; + QRect clipRect; + QPlatformTextureList::Flags flags; ++ QSurfaceFormat::ColorSpace colorSpace; + }; + + Q_DECLARE_TYPEINFO(QBackingstoreTextureInfo, Q_MOVABLE_TYPE); +@@ -181,6 +182,12 @@ QPlatformTextureList::Flags QPlatformTextureList::flags(int index) const + return d->textures.at(index).flags; + } + ++QSurfaceFormat::ColorSpace QPlatformTextureList::colorSpace(int index) const ++{ ++ Q_D(const QPlatformTextureList); ++ return d->textures.at(index).colorSpace; ++} ++ + QRect QPlatformTextureList::geometry(int index) const + { + Q_D(const QPlatformTextureList); +@@ -209,7 +216,7 @@ bool QPlatformTextureList::isLocked() const + } + + void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const QRect &geometry, +- const QRect &clipRect, Flags flags) ++ const QRect &clipRect, Flags flags, QSurfaceFormat::ColorSpace colorSpace) + { + Q_D(QPlatformTextureList); + QBackingstoreTextureInfo bi; +@@ -218,6 +225,7 @@ void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const Q + bi.rect = geometry; + bi.clipRect = clipRect; + bi.flags = flags; ++ bi.colorSpace = colorSpace; + d->textures.append(bi); + } + +@@ -300,6 +308,7 @@ static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, + if (srgb && canUseSrgb) + funcs->glEnable(GL_FRAMEBUFFER_SRGB); + ++ blitter->rebind(GL_TEXTURE_2D, textures->colorSpace(idx), window->format().colorSpace()); + blitter->blit(textures->textureId(idx), target, source); + + if (srgb && canUseSrgb) +@@ -433,6 +442,11 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion ®i + funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); + + if (textureId) { ++ // GUI texture is always in sRGB color space ++ d_ptr->blitter->rebind(GL_TEXTURE_2D, ++ QSurfaceFormat::sRGBColorSpace, ++ window->format().colorSpace()); ++ + if (d_ptr->needsSwizzle) + d_ptr->blitter->setRedBlueSwizzle(true); + // The backingstore is for the entire tlw. +diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h +index de5ba964dc..f8887bd4cd 100644 +--- a/src/gui/painting/qplatformbackingstore.h ++++ b/src/gui/painting/qplatformbackingstore.h +@@ -95,11 +95,13 @@ public: + QRect clipRect(int index) const; + void *source(int index); + Flags flags(int index) const; ++ QSurfaceFormat::ColorSpace colorSpace(int index) const; + void lock(bool on); + bool isLocked() const; + + void appendTexture(void *source, GLuint textureId, const QRect &geometry, +- const QRect &clipRect = QRect(), Flags flags = 0); ++ const QRect &clipRect = QRect(), Flags flags = 0, ++ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::DefaultColorSpace); + void clear(); + + Q_SIGNALS: +diff --git a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp +index 40400e2a19..5d44e62455 100644 +--- a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp ++++ b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp +@@ -230,7 +230,7 @@ void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegi + m_textures->clear(); + for (int i = 0; i < textures->count(); ++i) + m_textures->appendTexture(textures->source(i), textures->textureId(i), textures->geometry(i), +- textures->clipRect(i), textures->flags(i)); ++ textures->clipRect(i), textures->flags(i), textures->colorSpace(i)); + + updateTexture(); + m_textures->appendTexture(nullptr, m_bsTexture, window->geometry()); +diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp +index 7aef74c507..787445a4d9 100644 +--- a/src/widgets/kernel/qopenglwidget.cpp ++++ b/src/widgets/kernel/qopenglwidget.cpp +@@ -568,7 +568,8 @@ public: + updateBehavior(QOpenGLWidget::NoPartialUpdate), + requestedSamples(0), + inPaintGL(false), +- textureFormat(0) ++ textureFormat(0), ++ textureColorSpace(QSurfaceFormat::DefaultColorSpace) + { + requestedFormat = QSurfaceFormat::defaultFormat(); + } +@@ -578,6 +579,7 @@ public: + + GLuint textureId() const override; + QPlatformTextureList::Flags textureListFlags() override; ++ QSurfaceFormat::ColorSpace colorSpace() const override; + + void initialize(); + void invokeUserPaint(); +@@ -609,6 +611,7 @@ public: + int requestedSamples; + bool inPaintGL; + GLenum textureFormat; ++ QSurfaceFormat::ColorSpace textureColorSpace; + }; + + void QOpenGLWidgetPaintDevicePrivate::beginPaint() +@@ -695,6 +698,11 @@ QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags() + return flags; + } + ++QSurfaceFormat::ColorSpace QOpenGLWidgetPrivate::colorSpace() const ++{ ++ return textureColorSpace; ++} ++ + void QOpenGLWidgetPrivate::reset() + { + Q_Q(QOpenGLWidget); +@@ -1115,6 +1123,41 @@ void QOpenGLWidget::setTextureFormat(GLenum texFormat) + d->textureFormat = texFormat; + } + ++/*! ++ \return the declared color space of the internal texture of the widget. ++ ++ The texture's color space will be used when composing the widget ++ into the root window surface. ++ ++ \note when the color space is set to QSurfaceFormat::DefaultColorSpace, ++ color conversion is effectively disabled. ++ ++ \since 5.99 ++ */ ++QSurfaceFormat::ColorSpace QOpenGLWidget::textureColorSpace() const ++{ ++ Q_D(const QOpenGLWidget); ++ return d->textureColorSpace; ++} ++ ++/*! ++ Sets a custom color space for the internal texture of the widget ++ ++ The color space of the texture will be compared against the color ++ space of the root surface and conversion will be performed if needed. ++ ++ \note setting the color space to QSurfaceFormat::DefaultColorSpace will ++ effectively disable color conversion when composing this texture on ++ screen. ++ ++ \since 5.99 ++ */ ++void QOpenGLWidget::setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace) ++{ ++ Q_D(QOpenGLWidget); ++ d->textureColorSpace = colorSpace; ++} ++ + /*! + \return the active internal texture format if the widget has already + initialized, the requested format if one was set but the widget has not yet +diff --git a/src/widgets/kernel/qopenglwidget.h b/src/widgets/kernel/qopenglwidget.h +index 9eb4a9ba5a..eff2d9796d 100644 +--- a/src/widgets/kernel/qopenglwidget.h ++++ b/src/widgets/kernel/qopenglwidget.h +@@ -75,6 +75,9 @@ public: + GLenum textureFormat() const; + void setTextureFormat(GLenum texFormat); + ++ QSurfaceFormat::ColorSpace textureColorSpace() const; ++ void setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace); ++ + bool isValid() const; + + void makeCurrent(); +diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h +index ae50624c04..67c43ca7a4 100644 +--- a/src/widgets/kernel/qwidget_p.h ++++ b/src/widgets/kernel/qwidget_p.h +@@ -655,6 +655,7 @@ public: + ? QPlatformTextureList::StacksOnTop + : QPlatformTextureList::Flags(0); + } ++ virtual QSurfaceFormat::ColorSpace colorSpace() const { return QSurfaceFormat::DefaultColorSpace; } + virtual QImage grabFramebuffer() { return QImage(); } + virtual void beginBackingStorePainting() { } + virtual void endBackingStorePainting() { } +diff --git a/src/widgets/kernel/qwidgetbackingstore.cpp b/src/widgets/kernel/qwidgetbackingstore.cpp +index a32eb2a03b..db60338034 100644 +--- a/src/widgets/kernel/qwidgetbackingstore.cpp ++++ b/src/widgets/kernel/qwidgetbackingstore.cpp +@@ -1007,7 +1007,7 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo + if (wd->renderToTexture) { + QPlatformTextureList::Flags flags = wd->textureListFlags(); + const QRect rect(widget->mapTo(tlw, QPoint()), widget->size()); +- widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags); ++ widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags, wd->colorSpace()); + } + + for (int i = 0; i < wd->children.size(); ++i) { +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch b/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch deleted file mode 100644 index 57d8103918..0000000000 --- a/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch +++ /dev/null @@ -1,3595 +0,0 @@ -From 6ca9249292cc29ac4169e236f0d28f50006ba3cb Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Sat, 8 Dec 2018 15:35:43 +0300 -Subject: [PATCH 04/27] Implement openGL surface color space selection in Angle - -WARNING: this patch actually means that the library must be build on - the system with at least DXGI 1.4 (DirectX 12 API) present - in SDK. Mingw64 7.3 supports that. - -1) D3D11 implementation of angle now supports GL_RGB10_A2 format - -2) Technically, Angle's EGL implementation now supports the following - display extensions: - * EGL_KHR_gl_colorspace - * EGL_EXT_gl_colorspace_scrgb_linear - * EGL_EXT_gl_colorspace_bt2020_pq - -3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute, - which allows selection one of four color modes: - * Linear --- just pass-through data to GPU - * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system - becomes clever and automatically converts linear framebuffer - attachments to sRGB space, as per EGL_KHR_gl_colorspace definition. - It is not possible to select sRGB without this extra "feature". - * scRGB --- p709-g10 color space. This mode is the only mode - supported in f16-bit mode (and it is also not supported in other - bit depths). - * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode. - -5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode: - * because non-flip mode is considered deprecated and HDR is not - supported in it; - * because in flip-discard mode partial updates from - SwapChain11::present() are not supported and return an error, - which is never checked :) - -6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because - flip modes are not available on Windows 7 and such old systems. - -Notes: - -eglCreatePixmapSurface() is not implemented in Angle, so the support is -not added. - -eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface() -do not have support for color spaces according to the extension wording -(and they are also not supported by Angle :) ) - -Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6 ---- - src/3rdparty/angle/src/common/platform.h | 14 +- - src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +- - src/3rdparty/angle/src/libANGLE/Caps.h | 9 + - .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +- - .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +- - .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 + - .../renderer/d3d/d3d11/Renderer11.cpp | 16 +- - .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +- - .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++- - .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +- - .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +- - .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +- - .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +- - .../angle/src/libANGLE/validationEGL.cpp | 53 ++ - ...-surface-color-space-selection-in-An.patch | 596 ++++++++++++++++++ - 15 files changed, 831 insertions(+), 20 deletions(-) - create mode 100644 src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch - -diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h -index fb251da5..2e179945 100644 ---- a/src/3rdparty/angle/src/common/platform.h -+++ b/src/3rdparty/angle/src/common/platform.h -@@ -59,12 +59,14 @@ - # endif - - # if defined(ANGLE_ENABLE_D3D11) --#include --#include --#include --#include --#include --#include -+# include -+# include -+# include -+# include -+# include -+# include -+# include // WARNING: This is actually D3D12! -+# include - # endif - - #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11) -diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp -index 44da2bbe..20884574 100644 ---- a/src/3rdparty/angle/src/libANGLE/Caps.cpp -+++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp -@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions() - displayTextureShareGroup(false), - createContextClientArrays(false), - programCacheControl(false), -- robustResourceInitialization(false) -+ robustResourceInitialization(false), -+ colorspaceSRGB(false), -+ colorspaceSCRGBLinear(false), -+ colorspaceBt2020PQ(false) - { - } - -@@ -1146,6 +1149,9 @@ std::vector DisplayExtensions::getStrings() const - InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings); - InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings); - InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings); -+ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings); -+ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings); -+ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings); - // TODO(jmadill): Enable this when complete. - //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings); - // clang-format on -diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h -index 64bdf971..8157af58 100644 ---- a/src/3rdparty/angle/src/libANGLE/Caps.h -+++ b/src/3rdparty/angle/src/libANGLE/Caps.h -@@ -692,6 +692,15 @@ struct DisplayExtensions - - // EGL_ANGLE_robust_resource_initialization - bool robustResourceInitialization; -+ -+ // EGL_KHR_gl_colorspace -+ bool colorspaceSRGB; -+ -+ // EGL_EXT_gl_colorspace_scrgb_linear -+ bool colorspaceSCRGBLinear; -+ -+ // EGL_EXT_gl_colorspace_bt2020_pq -+ bool colorspaceBt2020PQ; - }; - - struct DeviceExtensions -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h -index dcc98f2e..b8ee6356 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h -@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples) = 0; -+ EGLint samples, -+ EGLint colorSpace) = 0; - virtual egl::Error getD3DTextureInfo(const egl::Config *configuration, - IUnknown *d3dTexture, - EGLint *width, -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp -index 7657aef7..efd4dd1a 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp -@@ -22,6 +22,27 @@ - namespace rx - { - -+GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace) -+{ -+ GLenum result = baseFormat; -+ -+ /** -+ * If sRGB extension is supported, we should change the surface format -+ * to a specific one that does support automated gamma conversion. -+ * -+ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of -+ * textures in this format technically is not supported! -+ */ -+ if (display->getExtensions().colorspaceSRGB && -+ baseFormat == GL_RGBA8_OES && -+ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR) -+ { -+ result = GL_SRGB8_ALPHA8; -+ } -+ -+ return result; -+} -+ - SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, - RendererD3D *renderer, - egl::Display *display, -@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, - mDisplay(display), - mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE), - mOrientation(static_cast(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))), -- mRenderTargetFormat(state.config->renderTargetFormat), -+ mColorSpace(static_cast(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))), -+ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)), - mDepthStencilFormat(state.config->depthStencilFormat), - mSwapChain(nullptr), - mSwapIntervalDirty(true), -@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display) - - mSwapChain = - mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat, -- mDepthStencilFormat, mOrientation, mState.config->samples); -+ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace); - if (!mSwapChain) - { - return egl::EglBadAlloc(); -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h -index 01d25732..ccb793d4 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h -@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl - - bool mFixedSize; - GLint mOrientation; -+ EGLint mColorSpace; - - GLenum mRenderTargetFormat; - GLenum mDepthStencilFormat; -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp -index b0ef9abd..f0e497b5 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp -@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display) - mRenderer11DeviceCaps.supportsConstantBufferOffsets = false; - mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false; - mRenderer11DeviceCaps.supportsDXGI1_2 = false; -+ mRenderer11DeviceCaps.supportsDXGI1_4 = false; - mRenderer11DeviceCaps.B5G6R5support = 0; - mRenderer11DeviceCaps.B4G4R4A4support = 0; - mRenderer11DeviceCaps.B5G5R5A1support = 0; -@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice() - - // Gather stats on DXGI and D3D feature level - ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2); -+ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4); - - ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel); - -@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps() - IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject(mDxgiAdapter); - mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr); - SafeRelease(dxgiAdapter2); -+ -+ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject(mDxgiAdapter); -+ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr); -+ SafeRelease(dxgiAdapter3); - } - - gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig( -@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions - - // All D3D feature levels support robust resource init - outExtensions->robustResourceInitialization = true; -+ -+ // color space selection supported in DXGI 1.4 only -+ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4; -+ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4; -+ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4; - } - - gl::Error Renderer11::flush() -@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow, - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples) -+ EGLint samples, -+ EGLint colorSpace) - { - return new SwapChain11(this, GetAs(nativeWindow), shareHandle, d3dTexture, -- backBufferFormat, depthBufferFormat, orientation, samples); -+ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace); - } - - void *Renderer11::getD3DDevice() -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h -index a8c24e68..3516bf77 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h -@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps - - D3D_FEATURE_LEVEL featureLevel; - bool supportsDXGI1_2; // Support for DXGI 1.2 -+ bool supportsDXGI1_4; // Support for DXGI 1.4 - bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView - bool supportsConstantBufferOffsets; // Support for Constant buffer offset - bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader -@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples) override; -+ EGLint samples, -+ EGLint colorSpace) override; - egl::Error getD3DTextureInfo(const egl::Config *configuration, - IUnknown *d3dTexture, - EGLint *width, -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp -index dcfd0648..fc967b90 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp -@@ -18,6 +18,11 @@ - #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h" - #include "third_party/trace_event/trace_event.h" - -+#if 0 -+// used only for HDR metadata configuration options -+#include -+#endif -+ - // Precompiled shaders - #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h" - #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h" -@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer, - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples) -+ EGLint samples, -+ EGLint colorSpace) - : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat), - mRenderer(renderer), - mWidth(-1), - mHeight(-1), - mOrientation(orientation), -+ mColorSpace(colorSpace), - mAppCreatedShareHandle(mShareHandle != nullptr), - mSwapInterval(0), - mPassThroughResourcesInit(false), -@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context, - mSwapChain1 = d3d11::DynamicCastComObject(mSwapChain); - } - -+ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4) -+ { -+ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject(mSwapChain); -+ -+ printf("*** EGL colorSpace: 0x%X\n", mColorSpace); -+ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat); -+ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat()); -+ -+ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) { -+ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; -+ switch (mColorSpace) -+ { -+ case EGL_GL_COLORSPACE_SRGB_KHR: -+ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; -+ break; -+ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: -+ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; -+ break; -+ case EGL_GL_COLORSPACE_BT2020_PQ_EXT: -+ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; -+ break; -+ default: -+ ASSERT(0 && "Unsupported colorspace requested"); -+ } -+ -+ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace); -+ -+ UINT supported = 0; -+ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported); -+ ASSERT(SUCCEEDED(result)); -+ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { -+ SafeRelease(swapChain3); -+ return EGL_BAD_MATCH; -+ } -+ -+ result = swapChain3->SetColorSpace1(nativeColorSpace); -+ ASSERT(SUCCEEDED(result)); -+ } -+ -+ SafeRelease(swapChain3); -+ -+#if 0 -+ -+ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject(mSwapChain); -+ -+ DXGI_HDR_METADATA_HDR10 md; -+ md.RedPrimary[0] = 0.680 * 50000; -+ md.RedPrimary[1] = 0.320 * 50000; -+ md.GreenPrimary[0] = 0.265 * 50000; -+ md.GreenPrimary[1] = 0.690 * 50000; -+ md.BluePrimary[0] = 0.150 * 50000; -+ md.BluePrimary[1] = 0.060 * 50000; -+ md.WhitePoint[0] = 0.3127 * 50000; -+ md.WhitePoint[1] = 0.3290 * 50000; -+ md.MaxMasteringLuminance = 1000 * 10000; -+ md.MinMasteringLuminance = 0.001 * 10000; -+ md.MaxContentLightLevel = 1000; -+ md.MaxFrameAverageLightLevel = 200; -+ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md); -+ printf("*** Result hdr 0x%X\n", result); -+ SafeRelease(swapChain4); -+#endif -+ } -+ - ID3D11Texture2D *backbufferTex = nullptr; - result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), - reinterpret_cast(&backbufferTex)); - ASSERT(SUCCEEDED(result)); -+ -+ // TODO: recover rendering to sRGB -+ // -+ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc; -+ // offscreenRTVDesc.Format = getSwapChainNativeFormat(); -+ // -+ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) { -+ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) { -+ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; -+ // } -+ // -+ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) { -+ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; -+ // } -+ // } -+ // -+ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format); -+ - const auto &format = - d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps()); - mBackBufferTexture.set(backbufferTex, format); -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h -index eca06821..2a4b9ba2 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h -@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples); -+ EGLint samples, -+ EGLint colorSpace); - ~SwapChain11() override; - - EGLint resize(const gl::Context *context, -@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D - EGLint mWidth; - EGLint mHeight; - const EGLint mOrientation; -+ EGLint mColorSpace; - bool mAppCreatedShareHandle; - unsigned int mSwapInterval; - bool mPassThroughResourcesInit; -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp -index 5394e3d3..af52c41d 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp -@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, - - // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a - // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain. -+ // -+ // NOTE: in non-flip mode HDR rendering is not supported, so use it -+ // by default - IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject(factory); - if (factory2 != nullptr) - { -@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.BufferUsage = - DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER; -- swapChainDesc.BufferCount = 1; -+ swapChainDesc.BufferCount = 2; - swapChainDesc.Scaling = DXGI_SCALING_STRETCH; -- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; -+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - swapChainDesc.Flags = 0; - IDXGISwapChain1 *swapChain1 = nullptr; -@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, - } - - DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; -- swapChainDesc.BufferCount = 1; -+ swapChainDesc.BufferCount = 2; - swapChainDesc.BufferDesc.Format = format; - swapChainDesc.BufferDesc.Width = width; - swapChainDesc.BufferDesc.Height = height; -@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, - swapChainDesc.SampleDesc.Count = samples; - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.Windowed = TRUE; -+ -+ /** -+ * NOTE1: in discard mode the swap chain doesn't support partial -+ * presentatiopn with Present1() call. Though it is not a big -+ * problem, because in case DXGI 1.2 is supported this code is -+ * unreachable. -+ * -+ * NOTE2: Flip modes are not supported on Windows 7 and the like, -+ * so use a legacy mode as a fallback -+ */ - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - - HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain); -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp -index 75c62988..58596169 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp -@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow, - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples) -+ EGLint samples, -+ EGLint colorSpace) - { -+ UNUSED_VARIABLE(colorSpace); - return new SwapChain9(this, GetAs(nativeWindow), shareHandle, d3dTexture, - backBufferFormat, depthBufferFormat, orientation); - } -diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h -index 9ddee45f..ce4bb201 100644 ---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h -+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h -@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D - GLenum backBufferFormat, - GLenum depthBufferFormat, - EGLint orientation, -- EGLint samples) override; -+ EGLint samples, -+ EGLint colorSpace) override; - egl::Error getD3DTextureInfo(const egl::Config *configuration, - IUnknown *d3dTexture, - EGLint *width, -diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp -index 13a3a9e2..858d7ee9 100644 ---- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp -+++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp -@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin - "either EGL_TRUE or EGL_FALSE."; - } - break; -+ case EGL_GL_COLORSPACE: -+ -+ if (!displayExtensions.colorspaceSRGB) -+ { -+ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; -+ } -+ -+ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) -+ { -+ if (!displayExtensions.colorspaceSCRGBLinear) -+ { -+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; -+ } -+ } -+ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) -+ { -+ if (!displayExtensions.colorspaceBt2020PQ) -+ { -+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; -+ } -+ } -+ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) -+ { -+ return EglBadAttribute() << "Unknown EGL color space requested"; -+ } -+ break; - - default: - return EglBadAttribute(); -@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri - } - break; - -+ case EGL_GL_COLORSPACE: -+ -+ if (!displayExtensions.colorspaceSRGB) -+ { -+ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; -+ } -+ -+ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) -+ { -+ if (!displayExtensions.colorspaceSCRGBLinear) -+ { -+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; -+ } -+ } -+ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) -+ { -+ if (!displayExtensions.colorspaceBt2020PQ) -+ { -+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; -+ } -+ } -+ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) -+ { -+ return EglBadAttribute() << "Unknown EGL color space requested"; -+ } -+ break; -+ - default: - return EglBadAttribute(); - } -diff --git a/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch -new file mode 100644 -index 00000000..dfbe3626 ---- /dev/null -+++ b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch -@@ -0,0 +1,596 @@ -+From 05082a2affad3428e2ba4475a5c083e81a7730ab Mon Sep 17 00:00:00 2001 -+From: Dmitry Kazakov -+Date: Sat, 8 Dec 2018 15:35:43 +0300 -+Subject: [PATCH] Implement openGL surface color space selection in Angle -+ -+WARNING: this patch actually means that the library must be build on -+ the system with at least DXGI 1.4 (DirectX 12 API) present -+ in SDK. Mingw64 7.3 supports that. -+ -+1) D3D11 implementation of angle now supports GL_RGB10_A2 format -+ -+2) Technically, Angle's EGL implementation now supports the following -+ display extensions: -+ * EGL_KHR_gl_colorspace -+ * EGL_EXT_gl_colorspace_scrgb_linear -+ * EGL_EXT_gl_colorspace_bt2020_pq -+ -+3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute, -+ which allows selection one of four color modes: -+ * Linear --- just pass-through data to GPU -+ * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system -+ becomes clever and automatically converts linear framebuffer -+ attachments to sRGB space, as per EGL_KHR_gl_colorspace definition. -+ It is not possible to select sRGB without this extra "feature". -+ * scRGB --- p709-g10 color space. This mode is the only mode -+ supported in f16-bit mode (and it is also not supported in other -+ bit depths). -+ * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode. -+ -+5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode: -+ * because non-flip mode is considered deprecated and HDR is not -+ supported in it; -+ * because in flip-discard mode partial updates from -+ SwapChain11::present() are not supported and return an error, -+ which is never checked :) -+ -+6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because -+ flip modes are not available on Windows 7 and such old systems. -+ -+Notes: -+ -+eglCreatePixmapSurface() is not implemented in Angle, so the support is -+not added. -+ -+eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface() -+do not have support for color spaces according to the extension wording -+(and they are also not supported by Angle :) ) -+ -+Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6 -+--- -+ src/3rdparty/angle/src/common/platform.h | 14 +-- -+ src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +- -+ src/3rdparty/angle/src/libANGLE/Caps.h | 9 ++ -+ .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +- -+ .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +++++- -+ .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 + -+ .../renderer/d3d/d3d11/Renderer11.cpp | 16 +++- -+ .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +- -+ .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++++++++++++++++++- -+ .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +- -+ .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +++- -+ .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +- -+ .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +- -+ .../angle/src/libANGLE/validationEGL.cpp | 53 +++++++++++ -+ 14 files changed, 235 insertions(+), 20 deletions(-) -+ -+diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h -+index fb251da579..2e17994557 100644 -+--- a/src/3rdparty/angle/src/common/platform.h -++++ b/src/3rdparty/angle/src/common/platform.h -+@@ -59,12 +59,14 @@ -+ # endif -+ -+ # if defined(ANGLE_ENABLE_D3D11) -+-#include -+-#include -+-#include -+-#include -+-#include -+-#include -++# include -++# include -++# include -++# include -++# include -++# include -++# include // WARNING: This is actually D3D12! -++# include -+ # endif -+ -+ #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11) -+diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp -+index 44da2bbe27..2088457458 100644 -+--- a/src/3rdparty/angle/src/libANGLE/Caps.cpp -++++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp -+@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions() -+ displayTextureShareGroup(false), -+ createContextClientArrays(false), -+ programCacheControl(false), -+- robustResourceInitialization(false) -++ robustResourceInitialization(false), -++ colorspaceSRGB(false), -++ colorspaceSCRGBLinear(false), -++ colorspaceBt2020PQ(false) -+ { -+ } -+ -+@@ -1146,6 +1149,9 @@ std::vector DisplayExtensions::getStrings() const -+ InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings); -+ InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings); -+ InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings); -++ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings); -++ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings); -++ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings); -+ // TODO(jmadill): Enable this when complete. -+ //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings); -+ // clang-format on -+diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h -+index 64bdf97112..8157af5800 100644 -+--- a/src/3rdparty/angle/src/libANGLE/Caps.h -++++ b/src/3rdparty/angle/src/libANGLE/Caps.h -+@@ -692,6 +692,15 @@ struct DisplayExtensions -+ -+ // EGL_ANGLE_robust_resource_initialization -+ bool robustResourceInitialization; -++ -++ // EGL_KHR_gl_colorspace -++ bool colorspaceSRGB; -++ -++ // EGL_EXT_gl_colorspace_scrgb_linear -++ bool colorspaceSCRGBLinear; -++ -++ // EGL_EXT_gl_colorspace_bt2020_pq -++ bool colorspaceBt2020PQ; -+ }; -+ -+ struct DeviceExtensions -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h -+index dcc98f2ec6..b8ee635625 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h -+@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples) = 0; -++ EGLint samples, -++ EGLint colorSpace) = 0; -+ virtual egl::Error getD3DTextureInfo(const egl::Config *configuration, -+ IUnknown *d3dTexture, -+ EGLint *width, -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp -+index 7657aef79e..efd4dd1a24 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp -+@@ -22,6 +22,27 @@ -+ namespace rx -+ { -+ -++GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace) -++{ -++ GLenum result = baseFormat; -++ -++ /** -++ * If sRGB extension is supported, we should change the surface format -++ * to a specific one that does support automated gamma conversion. -++ * -++ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of -++ * textures in this format technically is not supported! -++ */ -++ if (display->getExtensions().colorspaceSRGB && -++ baseFormat == GL_RGBA8_OES && -++ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR) -++ { -++ result = GL_SRGB8_ALPHA8; -++ } -++ -++ return result; -++} -++ -+ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, -+ RendererD3D *renderer, -+ egl::Display *display, -+@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state, -+ mDisplay(display), -+ mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE), -+ mOrientation(static_cast(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))), -+- mRenderTargetFormat(state.config->renderTargetFormat), -++ mColorSpace(static_cast(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))), -++ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)), -+ mDepthStencilFormat(state.config->depthStencilFormat), -+ mSwapChain(nullptr), -+ mSwapIntervalDirty(true), -+@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display) -+ -+ mSwapChain = -+ mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat, -+- mDepthStencilFormat, mOrientation, mState.config->samples); -++ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace); -+ if (!mSwapChain) -+ { -+ return egl::EglBadAlloc(); -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h -+index 01d2573244..ccb793d423 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h -+@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl -+ -+ bool mFixedSize; -+ GLint mOrientation; -++ EGLint mColorSpace; -+ -+ GLenum mRenderTargetFormat; -+ GLenum mDepthStencilFormat; -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp -+index b0ef9abddc..f0e497b52f 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp -+@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display) -+ mRenderer11DeviceCaps.supportsConstantBufferOffsets = false; -+ mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false; -+ mRenderer11DeviceCaps.supportsDXGI1_2 = false; -++ mRenderer11DeviceCaps.supportsDXGI1_4 = false; -+ mRenderer11DeviceCaps.B5G6R5support = 0; -+ mRenderer11DeviceCaps.B4G4R4A4support = 0; -+ mRenderer11DeviceCaps.B5G5R5A1support = 0; -+@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice() -+ -+ // Gather stats on DXGI and D3D feature level -+ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2); -++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4); -+ -+ ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel); -+ -+@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps() -+ IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject(mDxgiAdapter); -+ mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr); -+ SafeRelease(dxgiAdapter2); -++ -++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject(mDxgiAdapter); -++ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr); -++ SafeRelease(dxgiAdapter3); -+ } -+ -+ gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig( -+@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions -+ -+ // All D3D feature levels support robust resource init -+ outExtensions->robustResourceInitialization = true; -++ -++ // color space selection supported in DXGI 1.4 only -++ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4; -++ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4; -++ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4; -+ } -+ -+ gl::Error Renderer11::flush() -+@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow, -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples) -++ EGLint samples, -++ EGLint colorSpace) -+ { -+ return new SwapChain11(this, GetAs(nativeWindow), shareHandle, d3dTexture, -+- backBufferFormat, depthBufferFormat, orientation, samples); -++ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace); -+ } -+ -+ void *Renderer11::getD3DDevice() -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h -+index a8c24e681b..3516bf779d 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h -+@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps -+ -+ D3D_FEATURE_LEVEL featureLevel; -+ bool supportsDXGI1_2; // Support for DXGI 1.2 -++ bool supportsDXGI1_4; // Support for DXGI 1.4 -+ bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView -+ bool supportsConstantBufferOffsets; // Support for Constant buffer offset -+ bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader -+@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples) override; -++ EGLint samples, -++ EGLint colorSpace) override; -+ egl::Error getD3DTextureInfo(const egl::Config *configuration, -+ IUnknown *d3dTexture, -+ EGLint *width, -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp -+index dcfd06484d..fc967b90d0 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp -+@@ -18,6 +18,11 @@ -+ #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h" -+ #include "third_party/trace_event/trace_event.h" -+ -++#if 0 -++// used only for HDR metadata configuration options -++#include -++#endif -++ -+ // Precompiled shaders -+ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h" -+ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h" -+@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer, -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples) -++ EGLint samples, -++ EGLint colorSpace) -+ : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat), -+ mRenderer(renderer), -+ mWidth(-1), -+ mHeight(-1), -+ mOrientation(orientation), -++ mColorSpace(colorSpace), -+ mAppCreatedShareHandle(mShareHandle != nullptr), -+ mSwapInterval(0), -+ mPassThroughResourcesInit(false), -+@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context, -+ mSwapChain1 = d3d11::DynamicCastComObject(mSwapChain); -+ } -+ -++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4) -++ { -++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject(mSwapChain); -++ -++ printf("*** EGL colorSpace: 0x%X\n", mColorSpace); -++ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat); -++ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat()); -++ -++ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) { -++ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; -++ switch (mColorSpace) -++ { -++ case EGL_GL_COLORSPACE_SRGB_KHR: -++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; -++ break; -++ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: -++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; -++ break; -++ case EGL_GL_COLORSPACE_BT2020_PQ_EXT: -++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; -++ break; -++ default: -++ ASSERT(0 && "Unsupported colorspace requested"); -++ } -++ -++ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace); -++ -++ UINT supported = 0; -++ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported); -++ ASSERT(SUCCEEDED(result)); -++ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { -++ SafeRelease(swapChain3); -++ return EGL_BAD_MATCH; -++ } -++ -++ result = swapChain3->SetColorSpace1(nativeColorSpace); -++ ASSERT(SUCCEEDED(result)); -++ } -++ -++ SafeRelease(swapChain3); -++ -++#if 0 -++ -++ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject(mSwapChain); -++ -++ DXGI_HDR_METADATA_HDR10 md; -++ md.RedPrimary[0] = 0.680 * 50000; -++ md.RedPrimary[1] = 0.320 * 50000; -++ md.GreenPrimary[0] = 0.265 * 50000; -++ md.GreenPrimary[1] = 0.690 * 50000; -++ md.BluePrimary[0] = 0.150 * 50000; -++ md.BluePrimary[1] = 0.060 * 50000; -++ md.WhitePoint[0] = 0.3127 * 50000; -++ md.WhitePoint[1] = 0.3290 * 50000; -++ md.MaxMasteringLuminance = 1000 * 10000; -++ md.MinMasteringLuminance = 0.001 * 10000; -++ md.MaxContentLightLevel = 1000; -++ md.MaxFrameAverageLightLevel = 200; -++ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md); -++ printf("*** Result hdr 0x%X\n", result); -++ SafeRelease(swapChain4); -++#endif -++ } -++ -+ ID3D11Texture2D *backbufferTex = nullptr; -+ result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), -+ reinterpret_cast(&backbufferTex)); -+ ASSERT(SUCCEEDED(result)); -++ -++ // TODO: recover rendering to sRGB -++ // -++ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc; -++ // offscreenRTVDesc.Format = getSwapChainNativeFormat(); -++ // -++ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) { -++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) { -++ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; -++ // } -++ // -++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) { -++ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; -++ // } -++ // } -++ // -++ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format); -++ -+ const auto &format = -+ d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps()); -+ mBackBufferTexture.set(backbufferTex, format); -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h -+index eca068210b..2a4b9ba274 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h -+@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples); -++ EGLint samples, -++ EGLint colorSpace); -+ ~SwapChain11() override; -+ -+ EGLint resize(const gl::Context *context, -+@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D -+ EGLint mWidth; -+ EGLint mHeight; -+ const EGLint mOrientation; -++ EGLint mColorSpace; -+ bool mAppCreatedShareHandle; -+ unsigned int mSwapInterval; -+ bool mPassThroughResourcesInit; -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp -+index 5394e3d3e7..af52c41d00 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp -+@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, -+ -+ // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a -+ // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain. -++ // -++ // NOTE: in non-flip mode HDR rendering is not supported, so use it -++ // by default -+ IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject(factory); -+ if (factory2 != nullptr) -+ { -+@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, -+ swapChainDesc.SampleDesc.Quality = 0; -+ swapChainDesc.BufferUsage = -+ DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER; -+- swapChainDesc.BufferCount = 1; -++ swapChainDesc.BufferCount = 2; -+ swapChainDesc.Scaling = DXGI_SCALING_STRETCH; -+- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; -++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; -+ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; -+ swapChainDesc.Flags = 0; -+ IDXGISwapChain1 *swapChain1 = nullptr; -+@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, -+ } -+ -+ DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; -+- swapChainDesc.BufferCount = 1; -++ swapChainDesc.BufferCount = 2; -+ swapChainDesc.BufferDesc.Format = format; -+ swapChainDesc.BufferDesc.Width = width; -+ swapChainDesc.BufferDesc.Height = height; -+@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device, -+ swapChainDesc.SampleDesc.Count = samples; -+ swapChainDesc.SampleDesc.Quality = 0; -+ swapChainDesc.Windowed = TRUE; -++ -++ /** -++ * NOTE1: in discard mode the swap chain doesn't support partial -++ * presentatiopn with Present1() call. Though it is not a big -++ * problem, because in case DXGI 1.2 is supported this code is -++ * unreachable. -++ * -++ * NOTE2: Flip modes are not supported on Windows 7 and the like, -++ * so use a legacy mode as a fallback -++ */ -+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; -+ -+ HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain); -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp -+index 75c6298868..58596169a8 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp -+@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow, -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples) -++ EGLint samples, -++ EGLint colorSpace) -+ { -++ UNUSED_VARIABLE(colorSpace); -+ return new SwapChain9(this, GetAs(nativeWindow), shareHandle, d3dTexture, -+ backBufferFormat, depthBufferFormat, orientation); -+ } -+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h -+index 9ddee45f0f..ce4bb201e5 100644 -+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h -++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h -+@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D -+ GLenum backBufferFormat, -+ GLenum depthBufferFormat, -+ EGLint orientation, -+- EGLint samples) override; -++ EGLint samples, -++ EGLint colorSpace) override; -+ egl::Error getD3DTextureInfo(const egl::Config *configuration, -+ IUnknown *d3dTexture, -+ EGLint *width, -+diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp -+index 13a3a9e280..858d7ee929 100644 -+--- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp -++++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp -+@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin -+ "either EGL_TRUE or EGL_FALSE."; -+ } -+ break; -++ case EGL_GL_COLORSPACE: -++ -++ if (!displayExtensions.colorspaceSRGB) -++ { -++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; -++ } -++ -++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) -++ { -++ if (!displayExtensions.colorspaceSCRGBLinear) -++ { -++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; -++ } -++ } -++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) -++ { -++ if (!displayExtensions.colorspaceBt2020PQ) -++ { -++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; -++ } -++ } -++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) -++ { -++ return EglBadAttribute() << "Unknown EGL color space requested"; -++ } -++ break; -+ -+ default: -+ return EglBadAttribute(); -+@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri -+ } -+ break; -+ -++ case EGL_GL_COLORSPACE: -++ -++ if (!displayExtensions.colorspaceSRGB) -++ { -++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform."; -++ } -++ -++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) -++ { -++ if (!displayExtensions.colorspaceSCRGBLinear) -++ { -++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform."; -++ } -++ } -++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT) -++ { -++ if (!displayExtensions.colorspaceBt2020PQ) -++ { -++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform."; -++ } -++ } -++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR) -++ { -++ return EglBadAttribute() << "Unknown EGL color space requested"; -++ } -++ break; -++ -+ default: -+ return EglBadAttribute(); -+ } -+-- -+2.20.1.windows.1 -+ --- -2.22.0.windows.1 - -From 50d5c432a7a7b793750b07b7638f88cb587fb37b Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Wed, 13 Feb 2019 16:56:11 +0300 -Subject: [PATCH 05/27] Implement color space selection for QSurfaceFormat - -With the patch one can select color space of openGL surface -which is used a a root surface of the underlying native window. - -This feature is needed, e.g. when the user wants to render HDR -content on screen. In such a case OS should be instructed about -how to treat the graphical data in the application framebuffer. - -The easiest approach is to call QSurfaceFormat::setDefaultFormat() -before creating the first application window. In such a case the -root surface will (may) be in the requested format. - -Supported color spaces/formats: - -1) sRGB, SDR -2) scRGB (Rec 709, gamma 1.0), HDR -3) Rec 2020 PQ, HDR - -Please take into account that in real life the user should select -proper bit depth for each color space, otherwise the system will -refuse to create the surface: - -1) sRGB --- 8 bit or 10 bit -2) scRGB --- 16 bit only -3) Rec 2020 PQ --- 10 bit only - -Please note that color space selection is supported only on -platforms with DXGI 1.4 and higher. - -Change-Id: I5f4945db9798d542f19c8ff1af1effa34f7745fd ---- - src/gui/kernel/qsurfaceformat.cpp | 11 ++++ - src/gui/kernel/qsurfaceformat.h | 4 +- - src/gui/opengl/qopenglframebufferobject.cpp | 7 ++- - .../platforms/windows/qwindowseglcontext.cpp | 57 +++++++++++++++++-- - .../platforms/windows/qwindowseglcontext.h | 6 +- - .../platforms/windows/qwindowsopenglcontext.h | 2 +- - .../platforms/windows/qwindowswindow.cpp | 8 ++- - 7 files changed, 83 insertions(+), 12 deletions(-) - -diff --git a/src/gui/kernel/qsurfaceformat.cpp b/src/gui/kernel/qsurfaceformat.cpp -index 1a814ec2..fc8b9c4f 100644 ---- a/src/gui/kernel/qsurfaceformat.cpp -+++ b/src/gui/kernel/qsurfaceformat.cpp -@@ -221,6 +221,17 @@ public: - set, the window will be created with an sRGB-capable default - framebuffer. Note that some platforms may return windows with a sRGB-capable - default framebuffer even when not requested explicitly. -+ -+ \value scRGBColorSpace When \c{EGL_EXT_gl_colorspace_scrgb_linear} -+ is supported by the platform and this value is set, the window will -+ be created with an scRGB-capable default framebuffer. Note that some -+ platforms may return windows with a scRGB-capable default framebuffer -+ even when not requested explicitly. It usually happens when the application -+ requests 16-bit surface format. -+ -+ \value bt2020PQColorSpace When \c{EGL_EXT_gl_colorspace_bt2020_pq} -+ is supported by the platform and this value is set, the window will -+ be created with an bt2020 PQ default framebuffer. - */ - - /*! -diff --git a/src/gui/kernel/qsurfaceformat.h b/src/gui/kernel/qsurfaceformat.h -index ed63eb8b..9ba6a29b 100644 ---- a/src/gui/kernel/qsurfaceformat.h -+++ b/src/gui/kernel/qsurfaceformat.h -@@ -87,7 +87,9 @@ public: - - enum ColorSpace { - DefaultColorSpace, -- sRGBColorSpace -+ sRGBColorSpace, -+ scRGBColorSpace, -+ bt2020PQColorSpace - }; - Q_ENUM(ColorSpace) - -diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp -index cae3d516..ccdccb63 100644 ---- a/src/gui/opengl/qopenglframebufferobject.cpp -+++ b/src/gui/opengl/qopenglframebufferobject.cpp -@@ -545,10 +545,13 @@ void QOpenGLFramebufferObjectPrivate::initTexture(int idx) - ColorAttachment &color(colorAttachments[idx]); - - GLuint pixelType = GL_UNSIGNED_BYTE; -- if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) -+ if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) { - pixelType = GL_UNSIGNED_INT_2_10_10_10_REV; -- else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) -+ } else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) { - pixelType = GL_UNSIGNED_SHORT; -+ } else if (color.internalFormat == GL_RGBA16F) { -+ pixelType = GL_HALF_FLOAT; -+ } - - funcs.glTexImage2D(target, 0, color.internalFormat, color.size.width(), color.size.height(), 0, - GL_RGBA, pixelType, NULL); -diff --git a/src/plugins/platforms/windows/qwindowseglcontext.cpp b/src/plugins/platforms/windows/qwindowseglcontext.cpp -index 063e8115..4cd745ea 100644 ---- a/src/plugins/platforms/windows/qwindowseglcontext.cpp -+++ b/src/plugins/platforms/windows/qwindowseglcontext.cpp -@@ -151,8 +151,9 @@ bool QWindowsLibEGL::init() - eglGetCurrentDisplay = RESOLVE((EGLDisplay (EGLAPIENTRY *)(void)), eglGetCurrentDisplay); - eglSwapBuffers = RESOLVE((EGLBoolean (EGLAPIENTRY *)(EGLDisplay , EGLSurface)), eglSwapBuffers); - eglGetProcAddress = RESOLVE((QFunctionPointer (EGLAPIENTRY * )(const char *)), eglGetProcAddress); -+ eglQueryString = RESOLVE((const char* (EGLAPIENTRY *)(EGLDisplay, EGLint)), eglQueryString); - -- if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress) -+ if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress || !eglQueryString) - return false; - - eglGetPlatformDisplayEXT = nullptr; -@@ -197,8 +198,15 @@ bool QWindowsLibGLESv2::init() - } - - QWindowsEGLStaticContext::QWindowsEGLStaticContext(EGLDisplay display) -- : m_display(display) -+ : m_display(display), -+ m_hasSRGBColorSpaceSupport(false), -+ m_hasSCRGBColorSpaceSupport(false), -+ m_hasBt2020PQColorSpaceSupport(false) - { -+ const char *eglExtensions = libEGL.eglQueryString(display, EGL_EXTENSIONS); -+ m_hasSRGBColorSpaceSupport = strstr(eglExtensions, "EGL_KHR_gl_colorspace") != nullptr; -+ m_hasSCRGBColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_scrgb_linear") != nullptr; -+ m_hasBt2020PQColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_bt2020_pq") != nullptr; - } - - bool QWindowsEGLStaticContext::initializeAngle(QWindowsOpenGLTester::Renderers preferredType, HDC dc, -@@ -297,11 +305,48 @@ QWindowsOpenGLContext *QWindowsEGLStaticContext::createContext(QOpenGLContext *c - return new QWindowsEGLContext(this, context->format(), context->shareHandle()); - } - --void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) -+void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, -+ QSurfaceFormat::ColorSpace colorSpace, int *err) - { - *err = 0; -+ -+ EGLint eglColorSpace = EGL_GL_COLORSPACE_LINEAR_KHR; -+ bool colorSpaceSupported = false; -+ -+ switch (colorSpace) { -+ case QSurfaceFormat::DefaultColorSpace: -+ colorSpaceSupported = m_hasSRGBColorSpaceSupport; -+ break; -+ case QSurfaceFormat::sRGBColorSpace: -+ eglColorSpace = EGL_GL_COLORSPACE_SRGB_KHR; -+ colorSpaceSupported = m_hasSRGBColorSpaceSupport; -+ break; -+ case QSurfaceFormat::scRGBColorSpace: -+ eglColorSpace = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT; -+ colorSpaceSupported = m_hasSCRGBColorSpaceSupport; -+ break; -+ case QSurfaceFormat::bt2020PQColorSpace: -+ eglColorSpace = EGL_GL_COLORSPACE_BT2020_PQ_EXT; -+ colorSpaceSupported = m_hasBt2020PQColorSpaceSupport; -+ break; -+ } -+ -+ QVector attributes; -+ -+ if (colorSpaceSupported) { -+ attributes << EGL_GL_COLORSPACE << eglColorSpace; -+ } -+ -+ attributes << EGL_NONE; -+ -+ if (!colorSpaceSupported && colorSpace != QSurfaceFormat::DefaultColorSpace) { -+ qWarning().nospace() << __FUNCTION__ << ": Requested color space is not supported by EGL implementation: " << colorSpace << " (egl: 0x" << hex << eglColorSpace << ")"; -+ } -+ -+ - EGLSurface surface = libEGL.eglCreateWindowSurface(m_display, nativeConfig, -- static_cast(nativeWindow), nullptr); -+ static_cast(nativeWindow), -+ attributes.constData()); - if (surface == EGL_NO_SURFACE) { - *err = libEGL.eglGetError(); - qWarning("%s: Could not create the EGL window surface: 0x%x", __FUNCTION__, *err); -@@ -349,6 +394,7 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG - format.setSamples(sampleCount); - format.setStereo(false); - format.setSwapInterval(referenceFormat.swapInterval()); -+ format.setColorSpace(referenceFormat.colorSpace()); - - // Clear the EGL error state because some of the above may - // have errored out because the attribute is not applicable -@@ -378,7 +424,6 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG - \internal - \ingroup qt-lighthouse-win - */ -- - QWindowsEGLContext::QWindowsEGLContext(QWindowsEGLStaticContext *staticContext, - const QSurfaceFormat &format, - QPlatformOpenGLContext *share) -@@ -483,6 +528,8 @@ bool QWindowsEGLContext::makeCurrent(QPlatformSurface *surface) - // Simulate context loss as the context is useless. - QWindowsEGLStaticContext::libEGL.eglDestroyContext(m_eglDisplay, m_eglContext); - m_eglContext = EGL_NO_CONTEXT; -+ } else if (err == EGL_BAD_MATCH) { -+ qCDebug(lcQpaGl) << "Got bad match in createWindowSurface() for context" << this << "Check color space configuration."; - } - return false; - } -diff --git a/src/plugins/platforms/windows/qwindowseglcontext.h b/src/plugins/platforms/windows/qwindowseglcontext.h -index 8a1e1dda..9f7742e6 100644 ---- a/src/plugins/platforms/windows/qwindowseglcontext.h -+++ b/src/plugins/platforms/windows/qwindowseglcontext.h -@@ -80,6 +80,7 @@ struct QWindowsLibEGL - QFunctionPointer (EGLAPIENTRY *eglGetProcAddress)(const char *procname); - - EGLDisplay (EGLAPIENTRY * eglGetPlatformDisplayEXT)(EGLenum platform, void *native_display, const EGLint *attrib_list); -+ const char* (EGLAPIENTRY * eglQueryString)(EGLDisplay dpy, EGLint name); - - private: - #if !defined(QT_STATIC) || defined(QT_OPENGL_DYNAMIC) -@@ -121,7 +122,7 @@ public: - void *moduleHandle() const override { return libGLESv2.moduleHandle(); } - QOpenGLContext::OpenGLModuleType moduleType() const override { return QOpenGLContext::LibGLES; } - -- void *createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) override; -+ void *createWindowSurface(void *nativeWindow, void *nativeConfig, QSurfaceFormat::ColorSpace colorSpace, int *err) override; - void destroyWindowSurface(void *nativeSurface) override; - - QSurfaceFormat formatFromConfig(EGLDisplay display, EGLConfig config, const QSurfaceFormat &referenceFormat); -@@ -135,6 +136,9 @@ private: - EGLDisplay *display, EGLint *major, EGLint *minor); - - const EGLDisplay m_display; -+ bool m_hasSRGBColorSpaceSupport; -+ bool m_hasSCRGBColorSpaceSupport; -+ bool m_hasBt2020PQColorSpaceSupport; - }; - - class QWindowsEGLContext : public QWindowsOpenGLContext -diff --git a/src/plugins/platforms/windows/qwindowsopenglcontext.h b/src/plugins/platforms/windows/qwindowsopenglcontext.h -index cc6d93d3..61c0e287 100644 ---- a/src/plugins/platforms/windows/qwindowsopenglcontext.h -+++ b/src/plugins/platforms/windows/qwindowsopenglcontext.h -@@ -63,7 +63,7 @@ public: - - // If the windowing system interface needs explicitly created window surfaces (like EGL), - // reimplement these. -- virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, int * /*err*/) { return 0; } -+ virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, QSurfaceFormat::ColorSpace /*colorSpace*/, int * /*err*/) { return 0; } - virtual void destroyWindowSurface(void * /*nativeSurface*/) { } - - protected: -diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp -index 0376e363..76a443a8 100644 ---- a/src/plugins/platforms/windows/qwindowswindow.cpp -+++ b/src/plugins/platforms/windows/qwindowswindow.cpp -@@ -2741,9 +2741,13 @@ void *QWindowsWindow::surface(void *nativeConfig, int *err) - return 0; - #endif - #ifndef QT_NO_OPENGL -+ -+ -+ - if (!m_surface) { -- if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) -- m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, err); -+ if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { -+ m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, m_format.colorSpace(), err); -+ } - } - - return m_surface; --- -2.22.0.windows.1 - -From db1a31e31a3ab3df369135ba0665b557a60691f7 Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Thu, 22 Nov 2018 15:47:48 +0300 -Subject: [PATCH 06/27] Implement color conversion for the backing store - texture - -If the window surface is not in sRGB mode, then the backing store -surface should be converted into the destinations color space. - -This patch adds the most popular color space transitions into -QOpenGLTextureBlitter. - -The patch also implements QOpenGLWidget::setTextureColorSpace(), -which notifies the compositor about the color space of non-native -openGL widgets, so that the data could be converted correctly. - -TODO: should we implement the same for QOpenGLWindow:: - setTextureColorSpace()? - -Note: -The channels should be swizzled into RGBA *before* applying the -color space conversion matrix. Otherwise the colors will be skewed, -because the conversion function for R and B channels is not the same. - -Change-Id: Icbf599952c93cc04de417d0c3790a65282741655 ---- - src/gui/opengl/qopengltextureblitter.cpp | 222 ++++++++++++++++-- - src/gui/opengl/qopengltextureblitter.h | 12 +- - src/gui/painting/qplatformbackingstore.cpp | 16 +- - src/gui/painting/qplatformbackingstore.h | 4 +- - .../qopenglcompositorbackingstore.cpp | 2 +- - src/widgets/kernel/qopenglwidget.cpp | 45 +++- - src/widgets/kernel/qopenglwidget.h | 3 + - src/widgets/kernel/qwidget_p.h | 1 + - src/widgets/kernel/qwidgetbackingstore.cpp | 2 +- - 9 files changed, 281 insertions(+), 26 deletions(-) - -diff --git a/src/gui/opengl/qopengltextureblitter.cpp b/src/gui/opengl/qopengltextureblitter.cpp -index b65df9dc..5f6dbff2 100644 ---- a/src/gui/opengl/qopengltextureblitter.cpp -+++ b/src/gui/opengl/qopengltextureblitter.cpp -@@ -131,14 +131,85 @@ static const char vertex_shader[] = - "}"; - - static const char fragment_shader[] = -- "varying highp vec2 uv;" -- "uniform sampler2D textureSampler;" -- "uniform bool swizzle;" -- "uniform highp float opacity;" -+ "varying highp vec2 uv;\n" -+ "uniform sampler2D textureSampler;\n" -+ "uniform bool swizzle;\n" -+ "uniform highp float opacity;\n" -+ "#if defined SCRGB_TO_SRGB\n" -+ "highp vec4 linearToSRGB(highp vec4 value)\n" -+ "{\n" -+ " bvec4 cutoff = lessThan(value, vec4(0.0031308));\n" -+ " const highp vec2 a1 = vec2(0.055, 0.0);\n" -+ " const highp vec2 c2 = vec2(1.055, 1.0);\n" -+ " const highp vec2 m3 = vec2(2.4, 1.0);\n" -+ " const highp vec2 c4 = vec2(12.92, 1.0);\n" -+ " highp vec4 higher = c2.xxxy * pow(value, 1.0 / m3.xxxy) - a1.xxxy;\n" -+ " highp vec4 lower = value * c4.xxxy;\n" -+ " return mix(higher, lower, vec4(cutoff));\n" -+ "}\n" -+ "#endif\n" -+ "#if defined SRGB_TO_SCRGB || defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n" -+ "highp vec4 sRgbToLinear(highp vec4 sRGB)\n" -+ "{\n" -+ " bvec4 cutoff = lessThan(sRGB, vec4(0.04045));\n" -+ " const highp vec2 a1 = vec2(0.055, 0.0);\n" -+ " const highp vec2 c2 = vec2(1.055, 1.0);\n" -+ " const highp vec2 m3 = vec2(2.4, 1.0);\n" -+ " const highp vec2 c4 = vec2(12.92, 1.0);\n" -+ " highp vec4 higher = pow((sRGB + a1.xxxy) / c2.xxxy, m3.xxxy);\n" -+ " highp vec4 lower = sRGB / c4.xxxy;\n" -+ " return mix(higher, lower, vec4(cutoff));\n" -+ "}\n" -+ "#endif\n" -+ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n" -+ "highp vec4 applySmpte2084Curve(highp vec4 L)\n" -+ "{" -+ " const highp vec2 m1 = vec2(2610.0 / 4096.0 / 4.0, 1.0);\n" -+ " const highp vec2 m2 = vec2(2523.0 / 4096.0 * 128.0, 1.0);\n" -+ " const highp vec2 a1 = vec2(3424.0 / 4096.0, 0.0);\n" -+ " const highp vec2 c2 = vec2(2413.0 / 4096.0 * 32.0, 1.0);\n" -+ " const highp vec2 c3 = vec2(2392.0 / 4096.0 * 32.0, 1.0);\n" -+ " const highp vec2 a4 = vec2(1.0, 0.0);\n" -+ " highp vec4 Lp = pow(L, m1.xxxy);\n" -+ " highp vec4 res = pow((a1.xxxy + c2.xxxy * Lp) / (a4.xxxy + c3.xxxy * Lp), m2.xxxy);\n" -+ " return res;" -+ "}\n" -+ "#endif\n" -+ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n" -+ "highp vec4 scRgbToBt2020pq(highp vec4 value)\n" -+ "{\n" -+ " const highp mat4 convMat = " -+ " mat4(0.627402, 0.069095, 0.016394, 0.0," -+ " 0.329292, 0.919544, 0.088028, 0.0," -+ " 0.043306, 0.011360, 0.895578, 0.0," -+ " 0.0, 0.0, 0.0, 1.0);" -+ "" -+ " value = convMat * value;\n" -+ " return applySmpte2084Curve(0.008 * value);" -+ "}\n" -+ "#endif\n" -+ "#if defined SRGB_TO_BT2020PQ\n" -+ "highp vec4 sRgbToBt2020pq(highp vec4 value)\n" -+ "{\n" -+ " value = sRgbToLinear(value);" -+ " return scRgbToBt2020pq(value);" -+ "}\n" -+ "#endif\n" -+ "\n" - "void main() {" - " highp vec4 tmpFragColor = texture2D(textureSampler,uv);" -- " tmpFragColor.a *= opacity;" -- " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;" -+ " tmpFragColor.a *= opacity;\n" -+ " tmpFragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;\n" -+ "#if defined SRGB_TO_SCRGB\n" -+ " tmpFragColor = sRgbToLinear(tmpFragColor);\n" -+ "#elif defined SRGB_TO_BT2020PQ\n" -+ " tmpFragColor = sRgbToBt2020pq(tmpFragColor);\n" -+ "#elif defined SCRGB_TO_BT2020PQ\n" -+ " tmpFragColor = scRgbToBt2020pq(tmpFragColor);\n" -+ "#elif defined SCRGB_TO_SRGB\n" -+ " tmpFragColor = linearToSRGB(tmpFragColor);\n" -+ "#endif\n" -+ " gl_FragColor = tmpFragColor;" - "}"; - - static const char fragment_shader_external_oes[] = -@@ -187,6 +258,23 @@ private: - GLenum m_target; - }; - -+class ColorSpaceConversion : public QPair -+{ -+public: -+ ColorSpaceConversion() { }; -+ ColorSpaceConversion(QSurfaceFormat::ColorSpace srcColorSpace, -+ QSurfaceFormat::ColorSpace dstColorSpace) -+ : QPair(srcColorSpace, dstColorSpace) -+ { } -+ -+ QSurfaceFormat::ColorSpace source() const { -+ return first; -+ } -+ QSurfaceFormat::ColorSpace destination() const { -+ return second; -+ } -+}; -+ - class QOpenGLTextureBlitterPrivate - { - public: -@@ -197,16 +285,29 @@ public: - }; - - enum ProgramIndex { -- TEXTURE_2D, -- TEXTURE_EXTERNAL_OES -+ TEXTURE_2D = 0, -+ TEXTURE_2D_SRGB_TO_SCRGB, -+ TEXTURE_2D_SCRGB_TO_SRGB, -+ TEXTURE_2D_SRGB_TO_BT2020PQ, -+ TEXTURE_2D_SCRGB_TO_BT2020PQ, -+ TEXTURE_EXTERNAL_OES, -+ -+ PROGRAM_COUNT - }; - - QOpenGLTextureBlitterPrivate() : - swizzle(false), - opacity(1.0f), - vao(new QOpenGLVertexArrayObject), -- currentTarget(TEXTURE_2D) -- { } -+ currentTarget(GL_NONE), -+ colorSpaceConversion(0) -+ { -+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::DefaultColorSpace, QSurfaceFormat::DefaultColorSpace); -+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::scRGBColorSpace); -+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::sRGBColorSpace); -+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace); -+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace); -+ } - - bool buildProgram(ProgramIndex idx, const char *vs, const char *fs); - -@@ -214,6 +315,7 @@ public: - void blit(GLuint texture, const QMatrix4x4 &vertexTransform, QOpenGLTextureBlitter::Origin origin); - - void prepareProgram(const QMatrix4x4 &vertexTransform); -+ int calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace); - - QOpenGLBuffer vertexBuffer; - QOpenGLBuffer textureBuffer; -@@ -239,18 +341,48 @@ public: - bool swizzle; - float opacity; - TextureMatrixUniform textureMatrixUniformState; -- } programs[2]; -+ } programs[PROGRAM_COUNT]; - bool swizzle; - float opacity; - QScopedPointer vao; - GLenum currentTarget; -+ -+ int colorSpaceConversion; -+ QVector supportedColorSpaceConversions; - }; - --static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target) -+int QOpenGLTextureBlitterPrivate::calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace) -+{ -+ // TODO: auto-detect destination color space of the surface -+ // in case of default color space -+ -+ // disable color management if at least one of the color -+ // spaces is declared as default -+ if (srcColorSpace == QSurfaceFormat::DefaultColorSpace || -+ dstColorSpace == QSurfaceFormat::DefaultColorSpace) { -+ -+ return 0; -+ } -+ -+ // disable color management if source and destination color -+ // spaces are the same -+ if (srcColorSpace == dstColorSpace) { -+ return 0; -+ } -+ -+ ColorSpaceConversion conversion(srcColorSpace, dstColorSpace); -+ return supportedColorSpaceConversions.indexOf(conversion); -+} -+ -+static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target, int colorSpaceConversion) - { - switch (target) { -- case GL_TEXTURE_2D: -- return QOpenGLTextureBlitterPrivate::TEXTURE_2D; -+ case GL_TEXTURE_2D: { -+ QOpenGLTextureBlitterPrivate::ProgramIndex index = -+ QOpenGLTextureBlitterPrivate::ProgramIndex( -+ int(QOpenGLTextureBlitterPrivate::TEXTURE_2D) + colorSpaceConversion); -+ return index; -+ } - case GL_TEXTURE_EXTERNAL_OES: - return QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES; - default: -@@ -261,7 +393,7 @@ static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GL - - void QOpenGLTextureBlitterPrivate::prepareProgram(const QMatrix4x4 &vertexTransform) - { -- Program *program = &programs[targetToProgramIndex(currentTarget)]; -+ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)]; - - vertexBuffer.bind(); - program->glProgram->setAttributeBuffer(program->vertexCoordAttribPos, GL_FLOAT, 0, 3, 0); -@@ -293,7 +425,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture, - TextureBinder binder(currentTarget, texture); - prepareProgram(vertexTransform); - -- Program *program = &programs[targetToProgramIndex(currentTarget)]; -+ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)]; - program->glProgram->setUniformValue(program->textureTransformUniformPos, textureTransform); - program->textureMatrixUniformState = User; - -@@ -307,7 +439,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture, - TextureBinder binder(currentTarget, texture); - prepareProgram(vertexTransform); - -- Program *program = &programs[targetToProgramIndex(currentTarget)]; -+ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)]; - if (origin == QOpenGLTextureBlitter::OriginTopLeft) { - if (program->textureMatrixUniformState != IdentityFlipped) { - QMatrix3x3 flipped; -@@ -408,6 +540,28 @@ bool QOpenGLTextureBlitter::create() - } else { - if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D, vertex_shader, fragment_shader)) - return false; -+ -+ // TODO: create non-default transformations on-demand -+ { -+ const QString shader = QString("#define SRGB_TO_SCRGB\n %1").arg(fragment_shader); -+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB, vertex_shader, shader.toLatin1().constData())) -+ return false; -+ } -+ { -+ const QString shader = QString("#define SCRGB_TO_SRGB\n %1").arg(fragment_shader); -+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_SRGB, vertex_shader, shader.toLatin1().constData())) -+ return false; -+ } -+ { -+ const QString shader = QString("#define SRGB_TO_BT2020PQ\n %1").arg(fragment_shader); -+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData())) -+ return false; -+ } -+ { -+ const QString shader = QString("#define SCRGB_TO_BT2020PQ\n %1").arg(fragment_shader); -+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData())) -+ return false; -+ } - if (supportsExternalOESTarget()) - if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES, vertex_shader, fragment_shader_external_oes)) - return false; -@@ -455,6 +609,8 @@ void QOpenGLTextureBlitter::destroy() - return; - Q_D(QOpenGLTextureBlitter); - d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram.reset(); -+ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB].glProgram.reset(); -+ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ].glProgram.reset(); - d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES].glProgram.reset(); - d->vertexBuffer.destroy(); - d->textureBuffer.destroy(); -@@ -484,15 +640,26 @@ bool QOpenGLTextureBlitter::supportsExternalOESTarget() const - - \sa release(), blit() - */ --void QOpenGLTextureBlitter::bind(GLenum target) -+void QOpenGLTextureBlitter::bind(GLenum target, -+ QSurfaceFormat::ColorSpace srcColorSpace, -+ QSurfaceFormat::ColorSpace dstColorSpace) - { - Q_D(QOpenGLTextureBlitter); - - if (d->vao->isCreated()) - d->vao->bind(); - -+ const int index = d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace); -+ -+ if (index >= 0) { -+ d->colorSpaceConversion = index; -+ } else { -+ qWarning() << "QOpenGLTextureBlitter::bind(): color space conversion is not supported" << srcColorSpace << dstColorSpace; -+ d->colorSpaceConversion = 0; // noop conversion -+ } -+ - d->currentTarget = target; -- QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target)]; -+ QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target, d->colorSpaceConversion)]; - p->glProgram->bind(); - - d->vertexBuffer.bind(); -@@ -506,6 +673,21 @@ void QOpenGLTextureBlitter::bind(GLenum target) - d->textureBuffer.release(); - } - -+void QOpenGLTextureBlitter::rebind(GLenum target, QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace) -+{ -+ Q_D(QOpenGLTextureBlitter); -+ -+ if (d->vao->isCreated() && -+ d->currentTarget == target && -+ d->colorSpaceConversion == d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace)) { -+ -+ // the blitter is already configured in the correct state, so just skip it -+ return; -+ } -+ -+ bind(target, srcColorSpace, dstColorSpace); -+} -+ - /*! - Unbinds the graphics resources used by the blitter. - -@@ -514,7 +696,7 @@ void QOpenGLTextureBlitter::bind(GLenum target) - void QOpenGLTextureBlitter::release() - { - Q_D(QOpenGLTextureBlitter); -- d->programs[targetToProgramIndex(d->currentTarget)].glProgram->release(); -+ d->programs[targetToProgramIndex(d->currentTarget, d->colorSpaceConversion)].glProgram->release(); - if (d->vao->isCreated()) - d->vao->release(); - } -diff --git a/src/gui/opengl/qopengltextureblitter.h b/src/gui/opengl/qopengltextureblitter.h -index 2f7c6b1a..3c87e4e2 100644 ---- a/src/gui/opengl/qopengltextureblitter.h -+++ b/src/gui/opengl/qopengltextureblitter.h -@@ -48,6 +48,9 @@ - #include - #include - -+// TODO: less includes!!! -+#include -+ - QT_BEGIN_NAMESPACE - - class QOpenGLTextureBlitterPrivate; -@@ -69,7 +72,14 @@ public: - - bool supportsExternalOESTarget() const; - -- void bind(GLenum target = GL_TEXTURE_2D); -+ void bind(GLenum target = GL_TEXTURE_2D, -+ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace, -+ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace); -+ -+ void rebind(GLenum target = GL_TEXTURE_2D, -+ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace, -+ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace); -+ - void release(); - - void setRedBlueSwizzle(bool swizzle); -diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp -index c71d8254..8dd96a66 100644 ---- a/src/gui/painting/qplatformbackingstore.cpp -+++ b/src/gui/painting/qplatformbackingstore.cpp -@@ -132,6 +132,7 @@ struct QBackingstoreTextureInfo - QRect rect; - QRect clipRect; - QPlatformTextureList::Flags flags; -+ QSurfaceFormat::ColorSpace colorSpace; - }; - - Q_DECLARE_TYPEINFO(QBackingstoreTextureInfo, Q_MOVABLE_TYPE); -@@ -181,6 +182,12 @@ QPlatformTextureList::Flags QPlatformTextureList::flags(int index) const - return d->textures.at(index).flags; - } - -+QSurfaceFormat::ColorSpace QPlatformTextureList::colorSpace(int index) const -+{ -+ Q_D(const QPlatformTextureList); -+ return d->textures.at(index).colorSpace; -+} -+ - QRect QPlatformTextureList::geometry(int index) const - { - Q_D(const QPlatformTextureList); -@@ -209,7 +216,7 @@ bool QPlatformTextureList::isLocked() const - } - - void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const QRect &geometry, -- const QRect &clipRect, Flags flags) -+ const QRect &clipRect, Flags flags, QSurfaceFormat::ColorSpace colorSpace) - { - Q_D(QPlatformTextureList); - QBackingstoreTextureInfo bi; -@@ -218,6 +225,7 @@ void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const Q - bi.rect = geometry; - bi.clipRect = clipRect; - bi.flags = flags; -+ bi.colorSpace = colorSpace; - d->textures.append(bi); - } - -@@ -300,6 +308,7 @@ static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, - if (srgb && canUseSrgb) - funcs->glEnable(GL_FRAMEBUFFER_SRGB); - -+ blitter->rebind(GL_TEXTURE_2D, textures->colorSpace(idx), window->format().colorSpace()); - blitter->blit(textures->textureId(idx), target, source); - - if (srgb && canUseSrgb) -@@ -433,6 +442,11 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion ®i - funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); - - if (textureId) { -+ // GUI texture is always in sRGB color space -+ d_ptr->blitter->rebind(GL_TEXTURE_2D, -+ QSurfaceFormat::sRGBColorSpace, -+ window->format().colorSpace()); -+ - if (d_ptr->needsSwizzle) - d_ptr->blitter->setRedBlueSwizzle(true); - // The backingstore is for the entire tlw. -diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h -index de5ba964..f8887bd4 100644 ---- a/src/gui/painting/qplatformbackingstore.h -+++ b/src/gui/painting/qplatformbackingstore.h -@@ -95,11 +95,13 @@ public: - QRect clipRect(int index) const; - void *source(int index); - Flags flags(int index) const; -+ QSurfaceFormat::ColorSpace colorSpace(int index) const; - void lock(bool on); - bool isLocked() const; - - void appendTexture(void *source, GLuint textureId, const QRect &geometry, -- const QRect &clipRect = QRect(), Flags flags = 0); -+ const QRect &clipRect = QRect(), Flags flags = 0, -+ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::DefaultColorSpace); - void clear(); - - Q_SIGNALS: -diff --git a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp -index 40400e2a..5d44e624 100644 ---- a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp -+++ b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp -@@ -230,7 +230,7 @@ void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegi - m_textures->clear(); - for (int i = 0; i < textures->count(); ++i) - m_textures->appendTexture(textures->source(i), textures->textureId(i), textures->geometry(i), -- textures->clipRect(i), textures->flags(i)); -+ textures->clipRect(i), textures->flags(i), textures->colorSpace(i)); - - updateTexture(); - m_textures->appendTexture(nullptr, m_bsTexture, window->geometry()); -diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp -index 89f86015..0abf707e 100644 ---- a/src/widgets/kernel/qopenglwidget.cpp -+++ b/src/widgets/kernel/qopenglwidget.cpp -@@ -568,7 +568,8 @@ public: - updateBehavior(QOpenGLWidget::NoPartialUpdate), - requestedSamples(0), - inPaintGL(false), -- textureFormat(0) -+ textureFormat(0), -+ textureColorSpace(QSurfaceFormat::DefaultColorSpace) - { - requestedFormat = QSurfaceFormat::defaultFormat(); - } -@@ -578,6 +579,7 @@ public: - - GLuint textureId() const override; - QPlatformTextureList::Flags textureListFlags() override; -+ QSurfaceFormat::ColorSpace colorSpace() const override; - - void initialize(); - void invokeUserPaint(); -@@ -609,6 +611,7 @@ public: - int requestedSamples; - bool inPaintGL; - GLenum textureFormat; -+ QSurfaceFormat::ColorSpace textureColorSpace; - }; - - void QOpenGLWidgetPaintDevicePrivate::beginPaint() -@@ -695,6 +698,11 @@ QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags() - return flags; - } - -+QSurfaceFormat::ColorSpace QOpenGLWidgetPrivate::colorSpace() const -+{ -+ return textureColorSpace; -+} -+ - void QOpenGLWidgetPrivate::reset() - { - Q_Q(QOpenGLWidget); -@@ -1115,6 +1123,41 @@ void QOpenGLWidget::setTextureFormat(GLenum texFormat) - d->textureFormat = texFormat; - } - -+/*! -+ \return the declared color space of the internal texture of the widget. -+ -+ The texture's color space will be used when composing the widget -+ into the root window surface. -+ -+ \note when the color space is set to QSurfaceFormat::DefaultColorSpace, -+ color conversion is effectively disabled. -+ -+ \since 5.99 -+ */ -+QSurfaceFormat::ColorSpace QOpenGLWidget::textureColorSpace() const -+{ -+ Q_D(const QOpenGLWidget); -+ return d->textureColorSpace; -+} -+ -+/*! -+ Sets a custom color space for the internal texture of the widget -+ -+ The color space of the texture will be compared against the color -+ space of the root surface and conversion will be performed if needed. -+ -+ \note setting the color space to QSurfaceFormat::DefaultColorSpace will -+ effectively disable color conversion when composing this texture on -+ screen. -+ -+ \since 5.99 -+ */ -+void QOpenGLWidget::setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace) -+{ -+ Q_D(QOpenGLWidget); -+ d->textureColorSpace = colorSpace; -+} -+ - /*! - \return the active internal texture format if the widget has already - initialized, the requested format if one was set but the widget has not yet -diff --git a/src/widgets/kernel/qopenglwidget.h b/src/widgets/kernel/qopenglwidget.h -index 9eb4a9ba..eff2d979 100644 ---- a/src/widgets/kernel/qopenglwidget.h -+++ b/src/widgets/kernel/qopenglwidget.h -@@ -75,6 +75,9 @@ public: - GLenum textureFormat() const; - void setTextureFormat(GLenum texFormat); - -+ QSurfaceFormat::ColorSpace textureColorSpace() const; -+ void setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace); -+ - bool isValid() const; - - void makeCurrent(); -diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h -index 7e4ea2cc..1ff5af42 100644 ---- a/src/widgets/kernel/qwidget_p.h -+++ b/src/widgets/kernel/qwidget_p.h -@@ -655,6 +655,7 @@ public: - ? QPlatformTextureList::StacksOnTop - : QPlatformTextureList::Flags(0); - } -+ virtual QSurfaceFormat::ColorSpace colorSpace() const { return QSurfaceFormat::DefaultColorSpace; } - virtual QImage grabFramebuffer() { return QImage(); } - virtual void beginBackingStorePainting() { } - virtual void endBackingStorePainting() { } -diff --git a/src/widgets/kernel/qwidgetbackingstore.cpp b/src/widgets/kernel/qwidgetbackingstore.cpp -index a32eb2a0..db603380 100644 ---- a/src/widgets/kernel/qwidgetbackingstore.cpp -+++ b/src/widgets/kernel/qwidgetbackingstore.cpp -@@ -1007,7 +1007,7 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo - if (wd->renderToTexture) { - QPlatformTextureList::Flags flags = wd->textureListFlags(); - const QRect rect(widget->mapTo(tlw, QPoint()), widget->size()); -- widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags); -+ widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags, wd->colorSpace()); - } - - for (int i = 0; i < wd->children.size(); ++i) { --- -2.22.0.windows.1 - -From cd8298f8ed3735396d9274a629df3664298d7554 Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Tue, 4 Dec 2018 20:11:34 +0300 -Subject: [PATCH 07/27] Return QScreen's HMONITOR handle via - QPlatformNativeInterface - -It is needed to be able to fetch extra information about the display via -DXGI interface. - -Change-Id: Id83982eb07ade157719e430d0abcc2613409a343 ---- - .../windows/qwindowsnativeinterface.cpp | 16 ++++++++++++++++ - .../platforms/windows/qwindowsnativeinterface.h | 1 + - src/plugins/platforms/windows/qwindowsscreen.cpp | 5 +++++ - src/plugins/platforms/windows/qwindowsscreen.h | 2 ++ - 4 files changed, 24 insertions(+) - -diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp -index ed945ec4..1c5be441 100644 ---- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp -+++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp -@@ -40,6 +40,7 @@ - #include "qwindowsnativeinterface.h" - #include "qwindowsclipboard.h" - #include "qwindowswindow.h" -+#include "qwindowsscreen.h" - #include "qwindowscontext.h" - #include "qwindowscursor.h" - #include "qwindowsopenglcontext.h" -@@ -124,6 +125,21 @@ void *QWindowsNativeInterface::nativeResourceForWindow(const QByteArray &resourc - return nullptr; - } - -+void *QWindowsNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) -+{ -+ if (!screen || !screen->handle()) { -+ qWarning("%s: '%s' requested for null screen or screen without handle.", __FUNCTION__, resource.constData()); -+ return 0; -+ } -+ QWindowsScreen *bs = static_cast(screen->handle()); -+ int type = resourceType(resource); -+ if (type == HandleType) -+ return bs->handle(); -+ -+ qWarning("%s: Invalid key '%s' requested.", __FUNCTION__, resource.constData()); -+ return 0; -+} -+ - #ifndef QT_NO_CURSOR - void *QWindowsNativeInterface::nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor) - { -diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.h b/src/plugins/platforms/windows/qwindowsnativeinterface.h -index e6f8aae8..ce395dc5 100644 ---- a/src/plugins/platforms/windows/qwindowsnativeinterface.h -+++ b/src/plugins/platforms/windows/qwindowsnativeinterface.h -@@ -74,6 +74,7 @@ public: - void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) override; - #endif - void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; -+ void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override; - #ifndef QT_NO_CURSOR - void *nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor) override; - #endif -diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp -index 0520f889..b70b0bbe 100644 ---- a/src/plugins/platforms/windows/qwindowsscreen.cpp -+++ b/src/plugins/platforms/windows/qwindowsscreen.cpp -@@ -321,6 +321,11 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) - } - } - -+HMONITOR QWindowsScreen::handle() const -+{ -+ return m_data.hMonitor; -+} -+ - QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry() - { - QRect result; -diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h -index 824bcb1a..33c9effa 100644 ---- a/src/plugins/platforms/windows/qwindowsscreen.h -+++ b/src/plugins/platforms/windows/qwindowsscreen.h -@@ -104,6 +104,8 @@ public: - - inline void handleChanges(const QWindowsScreenData &newData); - -+ HMONITOR handle() const; -+ - #ifndef QT_NO_CURSOR - QPlatformCursor *cursor() const override { return m_cursor.data(); } - const CursorPtr &cursorPtr() const { return m_cursor; } --- -2.22.0.windows.1 - -From f8cf8c0cf6bde2e81a31b3fabd4c435c87d10b1d Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Sun, 10 Feb 2019 22:55:59 +0300 -Subject: [PATCH 08/27] Implement a manual test for checking is HDR features - work - -Test plan: - -1) Run without arguments: `hdr-openglwidget.exe` - It should show you three rectangles: the left one should be HDR'ly - bright, the other ones should be SDR'ly dim and look exactly the same. - -3) Run in Bt. 2020 PQ mode: `hdr-openglwidget.exe --bt2020pq` - The result should look exactly the same. - -4) Run in SDR sRGB mode: `hdr-openglwidget.exe --srgb`. - All three images should look SDR'ly dim. - -NOTE: -Please note that the current implementation of SDR compositing -in QOpenGLTextureBlitter doesn't support user configuration for -SDR brightness from the system. This API is available for UWP -applications only. It means that when changing "SDR brightness" -slider in Windows' settings, the brightness of our SDR widget -will not change. More that that, it might even be different from -the brightness of other SDR applications. - -Change-Id: Idccc790937c9061ec618ab21f6b71bd0620cd2cc ---- - .../hdr-qopenglwidget/KisGLImageF16.cpp | 131 +++++++++ - .../manual/hdr-qopenglwidget/KisGLImageF16.h | 68 +++++ - .../hdr-qopenglwidget/KisGLImageWidget.cpp | 252 ++++++++++++++++++ - .../hdr-qopenglwidget/KisGLImageWidget.h | 77 ++++++ - .../hdr-qopenglwidget/hdr-openglwidget.pro | 20 ++ - .../kis_gl_image_widget.frag | 23 ++ - .../hdr-qopenglwidget/kis_gl_image_widget.qrc | 6 + - .../kis_gl_image_widget.vert | 17 ++ - tests/manual/hdr-qopenglwidget/main.cpp | 153 +++++++++++ - .../hdr-qopenglwidget/openglprobeutils.cpp | 139 ++++++++++ - .../hdr-qopenglwidget/openglprobeutils.h | 42 +++ - tests/manual/hdr-qopenglwidget/window.cpp | 219 +++++++++++++++ - tests/manual/hdr-qopenglwidget/window.h | 69 +++++ - tests/manual/manual.pro | 2 +- - 14 files changed, 1217 insertions(+), 1 deletion(-) - create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp - create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.h - create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp - create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.h - create mode 100644 tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro - create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag - create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc - create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert - create mode 100644 tests/manual/hdr-qopenglwidget/main.cpp - create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.cpp - create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.h - create mode 100644 tests/manual/hdr-qopenglwidget/window.cpp - create mode 100644 tests/manual/hdr-qopenglwidget/window.h - -diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp -new file mode 100644 -index 00000000..a84b676f ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp -@@ -0,0 +1,131 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#include "KisGLImageF16.h" -+ -+#include -+#include -+ -+struct KisGLImageF16::Private : public QSharedData -+{ -+ QSize size; -+ QByteArray data; -+}; -+ -+KisGLImageF16::KisGLImageF16() -+ : m_d(new Private) -+{ -+} -+ -+KisGLImageF16::KisGLImageF16(const QSize &size, bool clearPixels) -+ : m_d(new Private) -+{ -+ resize(size, clearPixels); -+} -+ -+KisGLImageF16::KisGLImageF16(int width, int height, bool clearPixels) -+ : KisGLImageF16(QSize(width, height), clearPixels) -+{ -+} -+ -+KisGLImageF16::KisGLImageF16(const KisGLImageF16 &rhs) -+ : m_d(rhs.m_d) -+{ -+} -+ -+KisGLImageF16 &KisGLImageF16::operator=(const KisGLImageF16 &rhs) -+{ -+ m_d = rhs.m_d; -+} -+ -+bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs) -+{ -+ return lhs.m_d == rhs.m_d; -+} -+ -+bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs) -+{ -+ return !(lhs == rhs); -+} -+ -+KisGLImageF16::~KisGLImageF16() -+{ -+} -+ -+void KisGLImageF16::clearPixels() -+{ -+ if (!m_d->data.isEmpty()) { -+ m_d->data.fill(0); -+ } -+} -+ -+void KisGLImageF16::resize(const QSize &size, bool clearPixels) -+{ -+ const int pixelSize = 2 * 4; -+ -+ m_d->size = size; -+ m_d->data.resize(size.width() * size.height() * pixelSize); -+ -+ if (clearPixels) { -+ m_d->data.fill(0); -+ } -+} -+ -+const qfloat16 *KisGLImageF16::constData() const -+{ -+ Q_ASSERT(!m_d->data.isNull()); -+ return reinterpret_cast(m_d->data.data()); -+} -+ -+qfloat16 *KisGLImageF16::data() -+{ -+ m_d->data.detach(); -+ Q_ASSERT(!m_d->data.isNull()); -+ -+ return reinterpret_cast(m_d->data.data()); -+} -+ -+QSize KisGLImageF16::size() const -+{ -+ return m_d->size; -+} -+ -+int KisGLImageF16::width() const -+{ -+ return m_d->size.width(); -+} -+ -+int KisGLImageF16::height() const -+{ -+ return m_d->size.height(); -+} -+ -+bool KisGLImageF16::isNull() const -+{ -+ return m_d->data.isNull(); -+} -diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.h b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h -new file mode 100644 -index 00000000..335e42ee ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h -@@ -0,0 +1,68 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#ifndef KISGLIMAGEF16_H -+#define KISGLIMAGEF16_H -+ -+#include -+#include -+ -+class QSize; -+ -+class KisGLImageF16 -+{ -+public: -+ KisGLImageF16(); -+ KisGLImageF16(const QSize &size, bool clearPixels = false); -+ KisGLImageF16(int width, int height, bool clearPixels = false); -+ KisGLImageF16(const KisGLImageF16 &rhs); -+ KisGLImageF16& operator=(const KisGLImageF16 &rhs); -+ -+ friend bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs); -+ friend bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs); -+ -+ ~KisGLImageF16(); -+ -+ void clearPixels(); -+ void resize(const QSize &size, bool clearPixels = false); -+ -+ const qfloat16* constData() const; -+ qfloat16* data(); -+ -+ QSize size() const; -+ int width() const; -+ int height() const; -+ -+ bool isNull() const; -+ -+private: -+ struct Private; -+ QSharedDataPointer m_d; -+}; -+ -+#endif // KISGLIMAGEF16_H -diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp -new file mode 100644 -index 00000000..da36ac16 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp -@@ -0,0 +1,252 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#include "KisGLImageWidget.h" -+ -+#include -+#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) -+{ -+ -+ qDebug() << "Crating gl widget"; -+ -+ setTextureFormat(GL_RGBA16F); -+ setTextureColorSpace(colorSpace); -+ -+ setUpdateBehavior(QOpenGLWidget::NoPartialUpdate); -+} -+ -+void KisGLImageWidget::initializeGL() -+{ -+ initializeOpenGLFunctions(); -+ -+ qDebug() << "Initialized with format:" << context()->format(); -+ -+ 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"; -+ 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::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(); -+ glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f); -+ glClear(GL_COLOR_BUFFER_BIT); -+ -+ if (!m_texture.isCreated()) return; -+ -+ glViewport(0, 0, width(), height()); -+ -+ 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; -+ } -+ -+ if (m_vao.isCreated()) { -+ m_texture.setFormat(QOpenGLTexture::RGBA16F); -+ m_texture.setSize(image.width(), image.height()); -+ m_texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::Float16); -+ m_texture.setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); -+ m_texture.setMagnificationFilter(QOpenGLTexture::Linear); -+ m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image.constData()); -+ updateGeometry(); -+ } -+} -+ -+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(); -+} -+ -+KisGLImageF16 KisGLImageWidget::image() const -+{ -+ return m_sourceImage; -+} -+ -diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h -new file mode 100644 -index 00000000..e807064c ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h -@@ -0,0 +1,77 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#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); -+ -+ void initializeGL() override; -+ void paintGL() override; -+ -+ void loadImage(const KisGLImageF16 &image); -+ -+ void paintEvent(QPaintEvent *event) override; -+ void resizeEvent(QResizeEvent *event) override; -+ -+ QSize sizeHint() const override; -+ -+ KisGLImageF16 image() const; -+ -+public Q_SLOTS: -+ -+private: -+ void updateVerticesBuffer(const QRect &rect); -+ -+private: -+ KisGLImageF16 m_sourceImage; -+ -+ QOpenGLShaderProgram m_shader; -+ QOpenGLVertexArrayObject m_vao; -+ QOpenGLBuffer m_verticesBuffer; -+ QOpenGLBuffer m_textureVerticesBuffer; -+ QOpenGLTexture m_texture; -+}; -+ -+#endif // KISGLIMAGEWIDGET_H -diff --git a/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro -new file mode 100644 -index 00000000..b418e54b ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro -@@ -0,0 +1,20 @@ -+QT += widgets widgets-private gui-private core-private -+ -+TARGET = hdr-openglwidget -+TEMPLATE = app -+ -+SOURCES += main.cpp \ -+ #hdr-openglwidget.cpp \ -+ openglprobeutils.cpp \ -+ KisGLImageWidget.cpp \ -+ KisGLImageF16.cpp \ -+ window.cpp -+ -+HEADERS += \ -+#hdr-openglwidget.h \ -+ openglprobeutils.h \ -+ KisGLImageWidget.h \ -+ KisGLImageF16.h \ -+ window.h -+ -+RESOURCES += kis_gl_image_widget.qrc -diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag -new file mode 100644 -index 00000000..b57c6570 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag -@@ -0,0 +1,23 @@ -+#ifndef USE_OPENGLES -+#define INATTR in -+#define OUTATTR out -+#define DECLARE_OUT_VAR out vec4 f_fragColor; -+#define OUT_VAR f_fragColor -+#define highp -+#define texture2D texture -+#else -+#define INATTR varying -+#define DECLARE_OUT_VAR -+#define OUT_VAR gl_FragColor -+#endif -+// vertices data -+INATTR highp vec4 textureCoordinates; -+uniform sampler2D f_tileTexture; -+DECLARE_OUT_VAR -+ -+void main() -+{ -+ // get the fragment color from the tile texture -+ highp vec4 color = texture2D(f_tileTexture, textureCoordinates.st); -+ OUT_VAR = vec4(color); -+} -diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc -new file mode 100644 -index 00000000..ab5b5719 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc -@@ -0,0 +1,6 @@ -+ -+ -+ kis_gl_image_widget.frag -+ kis_gl_image_widget.vert -+ -+ -diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert -new file mode 100644 -index 00000000..9578f479 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert -@@ -0,0 +1,17 @@ -+#ifndef USE_OPENGLES -+#define INATTR in -+#define OUTATTR out -+#define highp -+#else -+#define INATTR attribute -+#define OUTATTR varying -+#endif -+uniform mat4 viewProjectionMatrix; -+INATTR highp vec3 vertexPosition; -+INATTR highp vec2 texturePosition; -+OUTATTR highp vec4 textureCoordinates; -+void main() -+{ -+ textureCoordinates = vec4(texturePosition.x, texturePosition.y, 0.0, 1.0); -+ gl_Position = viewProjectionMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0); -+} -diff --git a/tests/manual/hdr-qopenglwidget/main.cpp b/tests/manual/hdr-qopenglwidget/main.cpp -new file mode 100644 -index 00000000..e517ef85 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/main.cpp -@@ -0,0 +1,153 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2016 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#include -+#include "window.h" -+ -+#include "openglprobeutils.h" -+#include -+ -+QSurfaceFormat generateSurfaceFormat(QSurfaceFormat::RenderableType renderer, -+ QSurfaceFormat::ColorSpace colorSpace, -+ int bitDepth) -+{ -+ QSurfaceFormat format; -+#ifdef Q_OS_MACOS -+ format.setVersion(3, 2); -+ format.setProfile(QSurfaceFormat::CoreProfile); -+#else -+ format.setVersion(3, 0); -+ format.setProfile(QSurfaceFormat::CoreProfile); -+#endif -+ format.setDepthBufferSize(24); -+ format.setStencilBufferSize(8); -+ -+ switch (bitDepth) { -+ case 8: -+ format.setRedBufferSize(8); -+ format.setGreenBufferSize(8); -+ format.setBlueBufferSize(8); -+ format.setAlphaBufferSize(8); -+ break; -+ case 10: -+ format.setRedBufferSize(10); -+ format.setGreenBufferSize(10); -+ format.setBlueBufferSize(10); -+ format.setAlphaBufferSize(2); -+ break; -+ case 16: -+ format.setRedBufferSize(16); -+ format.setGreenBufferSize(16); -+ format.setBlueBufferSize(16); -+ format.setAlphaBufferSize(16); -+ break; -+ default: -+ qFatal("Unsupported surface bit depth %d", bitDepth); -+ } -+ -+ format.setRenderableType(renderer); -+ format.setColorSpace(colorSpace); -+ -+ format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); -+ format.setSwapInterval(0); // Disable vertical refresh syncing -+ -+ return format; -+} -+ -+int main(int argc, char *argv[]) -+{ -+ QVector allFormats; -+ -+ QVector availableRenderers; -+ availableRenderers << QSurfaceFormat::OpenGL; -+ availableRenderers << QSurfaceFormat::OpenGLES; -+ -+ for (QSurfaceFormat::RenderableType renderer : availableRenderers) { -+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 8); -+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 8); -+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 10); -+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 10); -+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::scRGBColorSpace, 16); -+ } -+ -+ for (QSurfaceFormat format : allFormats) { -+ qDebug() << "Probing: " << format; -+ bool result = OpenGLProbeUtils::probeFormat(format, true); -+ qDebug() << " result:" << result; -+ } -+ -+ -+ if (argc > 1 && !strcmp(argv[1], "--sharecontext")) { -+ qDebug("Requesting all contexts to share"); -+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); -+ } -+ -+ QApplication a(argc, argv); -+ -+ QSurfaceFormat::RenderableType renderer = QSurfaceFormat::OpenGLES; -+ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::scRGBColorSpace; -+ int bitDepth = 16; -+ -+ -+ if (QCoreApplication::arguments().contains(QLatin1String("--scrgb"))) { -+ colorSpace = QSurfaceFormat::scRGBColorSpace; -+ bitDepth = 16; -+ } else if (QCoreApplication::arguments().contains(QLatin1String("--bt2020pq"))) { -+ colorSpace = QSurfaceFormat::bt2020PQColorSpace; -+ bitDepth = 10; -+ } else if (QCoreApplication::arguments().contains(QLatin1String("--srgb"))) { -+ colorSpace = QSurfaceFormat::sRGBColorSpace; -+ bitDepth = 8; -+ } -+ -+ if (QCoreApplication::arguments().contains(QLatin1String("--opengl"))) { -+ renderer = QSurfaceFormat::OpenGL; -+ } else if (QCoreApplication::arguments().contains(QLatin1String("--opengles"))) { -+ renderer = QSurfaceFormat::OpenGLES; -+ } -+ -+ QSurfaceFormat format = generateSurfaceFormat(renderer, colorSpace, bitDepth); -+ -+ if (QCoreApplication::arguments().contains(QLatin1String("--multisample"))) { -+ format.setSamples(4); -+ } -+ -+ if (format.renderableType() == QSurfaceFormat::OpenGL) { -+ QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); -+ } else if (format.renderableType() == QSurfaceFormat::OpenGLES) { -+ QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); -+ } -+ -+ qDebug() << "Requesting" << format.renderableType() << format; -+ QSurfaceFormat::setDefaultFormat(format); -+ -+ Window window; -+ window.show(); -+ -+ return a.exec(); -+} -diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp -new file mode 100644 -index 00000000..687cc089 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp -@@ -0,0 +1,139 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#include "openglprobeutils.h" -+ -+#include -+#include -+#include -+#include -+#include -+ -+namespace OpenGLProbeUtils { -+ -+namespace { -+ -+struct AppAttributeSetter -+{ -+ AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES) -+ : m_attribute(attribute), -+ m_oldValue(QCoreApplication::testAttribute(attribute)) -+ { -+ QCoreApplication::setAttribute(attribute, useOpenGLES); -+ } -+ -+ ~AppAttributeSetter() { -+ QCoreApplication::setAttribute(m_attribute, m_oldValue); -+ } -+ -+private: -+ Qt::ApplicationAttribute m_attribute; -+ bool m_oldValue = false; -+}; -+ -+struct SurfaceFormatSetter -+{ -+ SurfaceFormatSetter(const QSurfaceFormat &format) -+ : m_oldFormat(QSurfaceFormat::defaultFormat()) -+ { -+ QSurfaceFormat::setDefaultFormat(format); -+ } -+ -+ ~SurfaceFormatSetter() { -+ QSurfaceFormat::setDefaultFormat(m_oldFormat); -+ } -+ -+private: -+ QSurfaceFormat m_oldFormat; -+}; -+ -+} -+ -+bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs) -+{ -+ return lhs == rhs || -+ ((lhs == QSurfaceFormat::DefaultColorSpace || -+ lhs == QSurfaceFormat::sRGBColorSpace) && -+ (rhs == QSurfaceFormat::DefaultColorSpace || -+ rhs == QSurfaceFormat::sRGBColorSpace)); -+} -+ -+bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState) -+{ -+ QScopedPointer sharedContextSetter; -+ QScopedPointer glSetter; -+ QScopedPointer glesSetter; -+ QScopedPointer formatSetter; -+ QScopedPointer application; -+ -+ if (adjustGlobalState) { -+ sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false)); -+ -+ if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) { -+ glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES)); -+ glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES)); -+ } -+ -+ formatSetter.reset(new SurfaceFormatSetter(format)); -+ -+ int argc = 1; -+ QByteArray data("krita"); -+ char *argv = data.data(); -+ application.reset(new QApplication(argc, &argv)); -+ } -+ -+ QWindow surface; -+ surface.setFormat(format); -+ surface.setSurfaceType(QSurface::OpenGLSurface); -+ surface.create(); -+ QOpenGLContext context; -+ context.setFormat(format); -+ -+ -+ if (!context.create()) { -+ qCritical() << "OpenGL context cannot be created"; -+ return false; -+ } -+ if (!context.isValid()) { -+ qCritical() << "OpenGL context is not valid while checking Qt's OpenGL status"; -+ return false; -+ } -+ if (!context.makeCurrent(&surface)) { -+ qCritical() << "OpenGL context cannot be made current"; -+ return false; -+ } -+ -+ if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) { -+ qCritical() << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace(); -+ return false; -+ } -+ -+ return true; -+} -+ -+} -diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.h b/tests/manual/hdr-qopenglwidget/openglprobeutils.h -new file mode 100644 -index 00000000..3b2f1ec3 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.h -@@ -0,0 +1,42 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#ifndef OPENGLPROBEUTILS_H -+#define OPENGLPROBEUTILS_H -+ -+#include -+ -+namespace OpenGLProbeUtils -+{ -+ -+bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs); -+bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState); -+ -+}; -+ -+#endif // OPENGLPROBEUTILS_H -diff --git a/tests/manual/hdr-qopenglwidget/window.cpp b/tests/manual/hdr-qopenglwidget/window.cpp -new file mode 100644 -index 00000000..5729660a ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/window.cpp -@@ -0,0 +1,219 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#include "window.h" -+ -+#include "KisGLImageWidget.h" -+#include "KisGLImageF16.h" -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+Window::Window() -+{ -+ setWindowTitle("16 bit float QOpenGLWidget test"); -+ QMenu *menu = menuBar()->addMenu("File"); -+ QToolBar *tb = addToolBar("File"); -+ -+ m_quitAction = new QAction("Quit", this); -+ connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close())); -+ menu->addAction(m_quitAction); -+ tb->addAction(m_quitAction); -+ -+ QWidget *centralWidget = new QWidget(this); -+ QVBoxLayout *layout = new QVBoxLayout(centralWidget); -+ -+ QHBoxLayout *hLayout = new QHBoxLayout(centralWidget); -+ -+ m_imageWidget = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this); -+ m_imageWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); -+ hLayout->addWidget(m_imageWidget, 0, Qt::AlignLeft); -+ -+ m_imageWidgetSdr = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this); -+ m_imageWidgetSdr->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); -+ hLayout->addWidget(m_imageWidgetSdr, 0, Qt::AlignLeft); -+ -+ QImage image(QSize(255,255), QImage::Format_ARGB32); -+ image.fill(Qt::red); -+ -+ QLabel *label = new QLabel(this); -+ label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); -+ hLayout->addWidget(label, 0, Qt::AlignLeft); -+ -+ m_imageWidget->loadImage(initializeImage(false)); -+ m_imageWidgetSdr->loadImage(initializeImage(true)); -+ label->setPixmap(QPixmap::fromImage(convertToQImage(m_imageWidget->image()))); -+ -+ layout->addLayout(hLayout); -+ -+ m_lblContextInfo = new QLabel(this); -+ layout->addWidget(m_lblContextInfo); -+ -+ QLabel *lblNotes = new QLabel(this); -+ lblNotes->setWordWrap(true); -+ lblNotes->setText("* In SDR display mode all three images should look exactly the same\n" -+ "* In HDR display mode: image 1 should look brighter than the others " -+ "(it is HDR), images 2 and 3 should have exactly the same brightness and look"); -+ -+ layout->addWidget(lblNotes); -+ -+ centralWidget->setLayout(layout); -+ setCentralWidget(centralWidget); -+} -+ -+inline qfloat16 floatToFloat16(float x) { -+ qfloat16 result; -+ qFloatToFloat16(&result, &x, 1); -+ return result; -+} -+ -+inline float float16ToFloat(qfloat16 x) { -+ float result; -+ qFloatFromFloat16(&result, &x, 1); -+ return result; -+} -+ -+ -+KisGLImageF16 Window::initializeImage(bool cropRange) const -+{ -+ const int size = 256; -+ KisGLImageF16 image(size, size); -+ image.clearPixels(); -+ qfloat16 *pixelPtr = image.data(); -+ -+ for (int y = 0; y < size; y++) { -+ for (int x = 0; x < size; x++) { -+ qfloat16 *pxl = reinterpret_cast(pixelPtr); -+ -+ float hdrRedValue = 25.0f * std::pow(float(x) / size, 5.0f); -+ -+ if (cropRange) { -+ hdrRedValue = qMin(hdrRedValue, 1.0f); -+ } -+ -+ pxl[0] = floatToFloat16(hdrRedValue); -+ -+ if (y > size / 2) { -+ const float portion = (float(y) / size - 0.5f) * 2.0f; -+ const float value = qMin(1.0f, 0.2f + 1.8f * portion); -+ -+ pxl[1] = floatToFloat16(value); -+ pxl[2] = floatToFloat16(value); -+ } else { -+ pxl[1] = floatToFloat16(0.2); -+ pxl[2] = floatToFloat16(0.2); -+ } -+ -+ pxl[3] = floatToFloat16(1.0); -+ -+ pixelPtr += 4; -+ } -+ } -+ -+ return image; -+} -+ -+inline float linearToSRGB(float value) -+{ -+ if (value <= 0.0f) { -+ value = 0.0f; -+ } else if (value < 0.0031308f) { -+ value = value * 12.92f; -+ } else if (value < 1.0f) { -+ value = std::pow(value, 0.41666f) * 1.055f - 0.055f; -+ } else { -+ value = 1.0f; -+ } -+ return value; -+} -+ -+QImage Window::convertToQImage(const KisGLImageF16 &image) const -+{ -+ const QSize size = image.size(); -+ const qfloat16 *pixelPtr = image.constData(); -+ -+ QImage qimage(size, QImage::Format_ARGB32); -+ quint8 *qimagePixelPtr = qimage.bits(); -+ -+ -+ for (int y = 0; y < size.height(); y++) { -+ for (int x = 0; x < size.width(); x++) { -+ const qfloat16 *srcPxl = pixelPtr; -+ quint8 *dstPxl = qimagePixelPtr; -+ -+ auto convertChannel = [] (qfloat16 x) { -+ float value = float16ToFloat(x); -+ return quint8(linearToSRGB(value) * 255.0f); -+ }; -+ -+ dstPxl[0] = convertChannel(srcPxl[2]); -+ dstPxl[1] = convertChannel(srcPxl[1]); -+ dstPxl[2] = convertChannel(srcPxl[0]); -+ dstPxl[3] = convertChannel(srcPxl[3]); -+ -+ pixelPtr += 4; -+ qimagePixelPtr += 4; -+ } -+ } -+ -+ return qimage; -+} -+ -+void Window::updateSurfaceInfo() -+{ -+ const QSurfaceFormat format = m_imageWidget->context()->format(); -+ -+ m_lblContextInfo->setText( -+ QString("renderer: %1\ncolorSpace: %2\n\n") -+ .arg(format.renderableType() == QSurfaceFormat::OpenGL ? "openGL" : "openGL ES") -+ .arg(format.colorSpace() == QSurfaceFormat::sRGBColorSpace ? "sRGB" : -+ format.colorSpace() == QSurfaceFormat::scRGBColorSpace ? "scRGB" : -+ format.colorSpace() == QSurfaceFormat::bt2020PQColorSpace ? "Bt. 2020 PQ" : -+ "unknown")); -+} -+ -+void Window::showEvent(QShowEvent *ev) -+{ -+ QMainWindow::showEvent(ev); -+ -+ if (m_lblContextInfo->text().isEmpty()) { -+ updateSurfaceInfo(); -+ } -+} -diff --git a/tests/manual/hdr-qopenglwidget/window.h b/tests/manual/hdr-qopenglwidget/window.h -new file mode 100644 -index 00000000..fd8e5c03 ---- /dev/null -+++ b/tests/manual/hdr-qopenglwidget/window.h -@@ -0,0 +1,69 @@ -+/**************************************************************************** -+** -+** Copyright (C) 2019 The Qt Company Ltd. -+** Contact: https://www.qt.io/licensing/ -+** -+** This file is part of the test suite of the Qt Toolkit. -+** -+** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -+** Commercial License Usage -+** Licensees holding valid commercial Qt licenses may use this file in -+** accordance with the commercial license agreement provided with the -+** Software or, alternatively, in accordance with the terms contained in -+** a written agreement between you and The Qt Company. For licensing terms -+** and conditions see https://www.qt.io/terms-conditions. For further -+** information use the contact form at https://www.qt.io/contact-us. -+** -+** GNU General Public License Usage -+** Alternatively, this file may be used under the terms of the GNU -+** General Public License version 3 as published by the Free Software -+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -+** included in the packaging of this file. Please review the following -+** information to ensure the GNU General Public License requirements will -+** be met: https://www.gnu.org/licenses/gpl-3.0.html. -+** -+** $QT_END_LICENSE$ -+** -+****************************************************************************/ -+ -+#ifndef WINDOW_H -+#define WINDOW_H -+ -+#include -+ -+class QAction; -+ -+class GLWidget; -+class KisGLImageWidget; -+class KisGLImageF16; -+class QLabel; -+ -+class Window : public QMainWindow -+{ -+ Q_OBJECT -+ -+public: -+ Window(); -+ -+ void showEvent(QShowEvent *ev) override; -+ -+public Q_SLOTS: -+ -+ -+private: -+ KisGLImageF16 initializeImage(bool cropRange) const; -+ QImage convertToQImage(const KisGLImageF16 &image) const; -+ -+ void updateSurfaceInfo(); -+ -+private: -+ GLWidget *m_glWidget {0}; -+ QAction *m_openAction {0}; -+ QAction *m_quitAction {0}; -+ KisGLImageWidget *m_imageWidget; -+ KisGLImageWidget *m_imageWidgetSdr; -+ QLabel *m_lblContextInfo; -+ -+}; -+ -+#endif -diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro -index ab00a5ef..b202ed04 100644 ---- a/tests/manual/manual.pro -+++ b/tests/manual/manual.pro -@@ -59,7 +59,7 @@ qtabbar - - qtConfig(opengl) { - SUBDIRS += qopengltextureblitter -- qtConfig(egl): SUBDIRS += qopenglcontext -+ qtConfig(egl): SUBDIRS += qopenglcontext hdr-qopenglwidget - } - - win32: SUBDIRS -= network_remote_stresstest network_stresstest --- -2.22.0.windows.1 - -From 47bda028a372144467a775c57e1269c6b1b1080d Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Thu, 6 Dec 2018 16:16:27 +0300 -Subject: [PATCH 09/27] Fix notification of QDockWidget when it gets undocked - -Before the patch the notification was emitted only when the docker -was attached to the panel or changed a position on it. - -Change-Id: Id3ffbd2018a8e68844d174328dd1c4ceb7fa01d3 ---- - src/widgets/widgets/qdockwidget.cpp | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp -index 6c871aae..19fc2d16 100644 ---- a/src/widgets/widgets/qdockwidget.cpp -+++ b/src/widgets/widgets/qdockwidget.cpp -@@ -1171,6 +1171,8 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect - QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q); - if (mwlayout) - emit q->dockLocationChanged(mwlayout->dockWidgetArea(q)); -+ } else { -+ emit q->dockLocationChanged(Qt::NoDockWidgetArea); - } - } - --- -2.22.0.windows.1 - diff --git a/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch b/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch new file mode 100644 index 0000000000..92edcd0c75 --- /dev/null +++ b/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch @@ -0,0 +1,95 @@ +From a1131ad9ab08c86eb32ca3f294690a698470bda1 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Tue, 4 Dec 2018 20:11:34 +0300 +Subject: [PATCH 06/17] Return QScreen's HMONITOR handle via + QPlatformNativeInterface + +It is needed to be able to fetch extra information about the display via +DXGI interface. + +Change-Id: Id83982eb07ade157719e430d0abcc2613409a343 +--- + .../windows/qwindowsnativeinterface.cpp | 16 ++++++++++++++++ + .../platforms/windows/qwindowsnativeinterface.h | 1 + + src/plugins/platforms/windows/qwindowsscreen.cpp | 5 +++++ + src/plugins/platforms/windows/qwindowsscreen.h | 2 ++ + 4 files changed, 24 insertions(+) + +diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp +index ed945ec4b1..1c5be44150 100644 +--- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp ++++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp +@@ -40,6 +40,7 @@ + #include "qwindowsnativeinterface.h" + #include "qwindowsclipboard.h" + #include "qwindowswindow.h" ++#include "qwindowsscreen.h" + #include "qwindowscontext.h" + #include "qwindowscursor.h" + #include "qwindowsopenglcontext.h" +@@ -124,6 +125,21 @@ void *QWindowsNativeInterface::nativeResourceForWindow(const QByteArray &resourc + return nullptr; + } + ++void *QWindowsNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) ++{ ++ if (!screen || !screen->handle()) { ++ qWarning("%s: '%s' requested for null screen or screen without handle.", __FUNCTION__, resource.constData()); ++ return 0; ++ } ++ QWindowsScreen *bs = static_cast(screen->handle()); ++ int type = resourceType(resource); ++ if (type == HandleType) ++ return bs->handle(); ++ ++ qWarning("%s: Invalid key '%s' requested.", __FUNCTION__, resource.constData()); ++ return 0; ++} ++ + #ifndef QT_NO_CURSOR + void *QWindowsNativeInterface::nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor) + { +diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.h b/src/plugins/platforms/windows/qwindowsnativeinterface.h +index e6f8aae8fb..ce395dc5a4 100644 +--- a/src/plugins/platforms/windows/qwindowsnativeinterface.h ++++ b/src/plugins/platforms/windows/qwindowsnativeinterface.h +@@ -74,6 +74,7 @@ public: + void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) override; + #endif + void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; ++ void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override; + #ifndef QT_NO_CURSOR + void *nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor) override; + #endif +diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp +index b28a113ce6..2f8850cbe0 100644 +--- a/src/plugins/platforms/windows/qwindowsscreen.cpp ++++ b/src/plugins/platforms/windows/qwindowsscreen.cpp +@@ -323,6 +323,11 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) + } + } + ++HMONITOR QWindowsScreen::handle() const ++{ ++ return m_data.hMonitor; ++} ++ + QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry() + { + QRect result; +diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h +index 8ad012512e..3eb2d35b27 100644 +--- a/src/plugins/platforms/windows/qwindowsscreen.h ++++ b/src/plugins/platforms/windows/qwindowsscreen.h +@@ -104,6 +104,8 @@ public: + + inline void handleChanges(const QWindowsScreenData &newData); + ++ HMONITOR handle() const; ++ + #ifndef QT_NO_CURSOR + QPlatformCursor *cursor() const override { return m_cursor.data(); } + const CursorPtr &cursorPtr() const { return m_cursor; } +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch b/3rdparty/ext_qt/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch new file mode 100644 index 0000000000..9d2a59096a --- /dev/null +++ b/3rdparty/ext_qt/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch @@ -0,0 +1,1368 @@ +From fc02647fc01c20f3bfcc53f38a060328b9e2ab90 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sun, 10 Feb 2019 22:55:59 +0300 +Subject: [PATCH 07/17] Implement a manual test for checking is HDR features + work + +Test plan: + +1) Run without arguments: `hdr-openglwidget.exe` + It should show you three rectangles: the left one should be HDR'ly + bright, the other ones should be SDR'ly dim and look exactly the same. + +3) Run in Bt. 2020 PQ mode: `hdr-openglwidget.exe --bt2020pq` + The result should look exactly the same. + +4) Run in SDR sRGB mode: `hdr-openglwidget.exe --srgb`. + All three images should look SDR'ly dim. + +NOTE: +Please note that the current implementation of SDR compositing +in QOpenGLTextureBlitter doesn't support user configuration for +SDR brightness from the system. This API is available for UWP +applications only. It means that when changing "SDR brightness" +slider in Windows' settings, the brightness of our SDR widget +will not change. More that that, it might even be different from +the brightness of other SDR applications. + +Change-Id: Idccc790937c9061ec618ab21f6b71bd0620cd2cc +--- + .../hdr-qopenglwidget/KisGLImageF16.cpp | 131 +++++++++ + .../manual/hdr-qopenglwidget/KisGLImageF16.h | 68 +++++ + .../hdr-qopenglwidget/KisGLImageWidget.cpp | 252 ++++++++++++++++++ + .../hdr-qopenglwidget/KisGLImageWidget.h | 77 ++++++ + .../hdr-qopenglwidget/hdr-openglwidget.pro | 20 ++ + .../kis_gl_image_widget.frag | 23 ++ + .../hdr-qopenglwidget/kis_gl_image_widget.qrc | 6 + + .../kis_gl_image_widget.vert | 17 ++ + tests/manual/hdr-qopenglwidget/main.cpp | 153 +++++++++++ + .../hdr-qopenglwidget/openglprobeutils.cpp | 139 ++++++++++ + .../hdr-qopenglwidget/openglprobeutils.h | 42 +++ + tests/manual/hdr-qopenglwidget/window.cpp | 219 +++++++++++++++ + tests/manual/hdr-qopenglwidget/window.h | 69 +++++ + tests/manual/manual.pro | 2 +- + 14 files changed, 1217 insertions(+), 1 deletion(-) + create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp + create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.h + create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp + create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.h + create mode 100644 tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro + create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag + create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc + create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert + create mode 100644 tests/manual/hdr-qopenglwidget/main.cpp + create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.cpp + create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.h + create mode 100644 tests/manual/hdr-qopenglwidget/window.cpp + create mode 100644 tests/manual/hdr-qopenglwidget/window.h + +diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp +new file mode 100644 +index 0000000000..a84b676f5b +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp +@@ -0,0 +1,131 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "KisGLImageF16.h" ++ ++#include ++#include ++ ++struct KisGLImageF16::Private : public QSharedData ++{ ++ QSize size; ++ QByteArray data; ++}; ++ ++KisGLImageF16::KisGLImageF16() ++ : m_d(new Private) ++{ ++} ++ ++KisGLImageF16::KisGLImageF16(const QSize &size, bool clearPixels) ++ : m_d(new Private) ++{ ++ resize(size, clearPixels); ++} ++ ++KisGLImageF16::KisGLImageF16(int width, int height, bool clearPixels) ++ : KisGLImageF16(QSize(width, height), clearPixels) ++{ ++} ++ ++KisGLImageF16::KisGLImageF16(const KisGLImageF16 &rhs) ++ : m_d(rhs.m_d) ++{ ++} ++ ++KisGLImageF16 &KisGLImageF16::operator=(const KisGLImageF16 &rhs) ++{ ++ m_d = rhs.m_d; ++} ++ ++bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs) ++{ ++ return lhs.m_d == rhs.m_d; ++} ++ ++bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs) ++{ ++ return !(lhs == rhs); ++} ++ ++KisGLImageF16::~KisGLImageF16() ++{ ++} ++ ++void KisGLImageF16::clearPixels() ++{ ++ if (!m_d->data.isEmpty()) { ++ m_d->data.fill(0); ++ } ++} ++ ++void KisGLImageF16::resize(const QSize &size, bool clearPixels) ++{ ++ const int pixelSize = 2 * 4; ++ ++ m_d->size = size; ++ m_d->data.resize(size.width() * size.height() * pixelSize); ++ ++ if (clearPixels) { ++ m_d->data.fill(0); ++ } ++} ++ ++const qfloat16 *KisGLImageF16::constData() const ++{ ++ Q_ASSERT(!m_d->data.isNull()); ++ return reinterpret_cast(m_d->data.data()); ++} ++ ++qfloat16 *KisGLImageF16::data() ++{ ++ m_d->data.detach(); ++ Q_ASSERT(!m_d->data.isNull()); ++ ++ return reinterpret_cast(m_d->data.data()); ++} ++ ++QSize KisGLImageF16::size() const ++{ ++ return m_d->size; ++} ++ ++int KisGLImageF16::width() const ++{ ++ return m_d->size.width(); ++} ++ ++int KisGLImageF16::height() const ++{ ++ return m_d->size.height(); ++} ++ ++bool KisGLImageF16::isNull() const ++{ ++ return m_d->data.isNull(); ++} +diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.h b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h +new file mode 100644 +index 0000000000..335e42ee68 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h +@@ -0,0 +1,68 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#ifndef KISGLIMAGEF16_H ++#define KISGLIMAGEF16_H ++ ++#include ++#include ++ ++class QSize; ++ ++class KisGLImageF16 ++{ ++public: ++ KisGLImageF16(); ++ KisGLImageF16(const QSize &size, bool clearPixels = false); ++ KisGLImageF16(int width, int height, bool clearPixels = false); ++ KisGLImageF16(const KisGLImageF16 &rhs); ++ KisGLImageF16& operator=(const KisGLImageF16 &rhs); ++ ++ friend bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs); ++ friend bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs); ++ ++ ~KisGLImageF16(); ++ ++ void clearPixels(); ++ void resize(const QSize &size, bool clearPixels = false); ++ ++ const qfloat16* constData() const; ++ qfloat16* data(); ++ ++ QSize size() const; ++ int width() const; ++ int height() const; ++ ++ bool isNull() const; ++ ++private: ++ struct Private; ++ QSharedDataPointer m_d; ++}; ++ ++#endif // KISGLIMAGEF16_H +diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp +new file mode 100644 +index 0000000000..da36ac1619 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp +@@ -0,0 +1,252 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "KisGLImageWidget.h" ++ ++#include ++#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) ++{ ++ ++ qDebug() << "Crating gl widget"; ++ ++ setTextureFormat(GL_RGBA16F); ++ setTextureColorSpace(colorSpace); ++ ++ setUpdateBehavior(QOpenGLWidget::NoPartialUpdate); ++} ++ ++void KisGLImageWidget::initializeGL() ++{ ++ initializeOpenGLFunctions(); ++ ++ qDebug() << "Initialized with format:" << context()->format(); ++ ++ 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"; ++ 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::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(); ++ glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f); ++ glClear(GL_COLOR_BUFFER_BIT); ++ ++ if (!m_texture.isCreated()) return; ++ ++ glViewport(0, 0, width(), height()); ++ ++ 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; ++ } ++ ++ if (m_vao.isCreated()) { ++ m_texture.setFormat(QOpenGLTexture::RGBA16F); ++ m_texture.setSize(image.width(), image.height()); ++ m_texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::Float16); ++ m_texture.setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); ++ m_texture.setMagnificationFilter(QOpenGLTexture::Linear); ++ m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image.constData()); ++ updateGeometry(); ++ } ++} ++ ++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(); ++} ++ ++KisGLImageF16 KisGLImageWidget::image() const ++{ ++ return m_sourceImage; ++} ++ +diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h +new file mode 100644 +index 0000000000..e807064cb4 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h +@@ -0,0 +1,77 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#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); ++ ++ void initializeGL() override; ++ void paintGL() override; ++ ++ void loadImage(const KisGLImageF16 &image); ++ ++ void paintEvent(QPaintEvent *event) override; ++ void resizeEvent(QResizeEvent *event) override; ++ ++ QSize sizeHint() const override; ++ ++ KisGLImageF16 image() const; ++ ++public Q_SLOTS: ++ ++private: ++ void updateVerticesBuffer(const QRect &rect); ++ ++private: ++ KisGLImageF16 m_sourceImage; ++ ++ QOpenGLShaderProgram m_shader; ++ QOpenGLVertexArrayObject m_vao; ++ QOpenGLBuffer m_verticesBuffer; ++ QOpenGLBuffer m_textureVerticesBuffer; ++ QOpenGLTexture m_texture; ++}; ++ ++#endif // KISGLIMAGEWIDGET_H +diff --git a/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro +new file mode 100644 +index 0000000000..b418e54b43 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro +@@ -0,0 +1,20 @@ ++QT += widgets widgets-private gui-private core-private ++ ++TARGET = hdr-openglwidget ++TEMPLATE = app ++ ++SOURCES += main.cpp \ ++ #hdr-openglwidget.cpp \ ++ openglprobeutils.cpp \ ++ KisGLImageWidget.cpp \ ++ KisGLImageF16.cpp \ ++ window.cpp ++ ++HEADERS += \ ++#hdr-openglwidget.h \ ++ openglprobeutils.h \ ++ KisGLImageWidget.h \ ++ KisGLImageF16.h \ ++ window.h ++ ++RESOURCES += kis_gl_image_widget.qrc +diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag +new file mode 100644 +index 0000000000..b57c657046 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag +@@ -0,0 +1,23 @@ ++#ifndef USE_OPENGLES ++#define INATTR in ++#define OUTATTR out ++#define DECLARE_OUT_VAR out vec4 f_fragColor; ++#define OUT_VAR f_fragColor ++#define highp ++#define texture2D texture ++#else ++#define INATTR varying ++#define DECLARE_OUT_VAR ++#define OUT_VAR gl_FragColor ++#endif ++// vertices data ++INATTR highp vec4 textureCoordinates; ++uniform sampler2D f_tileTexture; ++DECLARE_OUT_VAR ++ ++void main() ++{ ++ // get the fragment color from the tile texture ++ highp vec4 color = texture2D(f_tileTexture, textureCoordinates.st); ++ OUT_VAR = vec4(color); ++} +diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc +new file mode 100644 +index 0000000000..ab5b5719a9 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc +@@ -0,0 +1,6 @@ ++ ++ ++ kis_gl_image_widget.frag ++ kis_gl_image_widget.vert ++ ++ +diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert +new file mode 100644 +index 0000000000..9578f47945 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert +@@ -0,0 +1,17 @@ ++#ifndef USE_OPENGLES ++#define INATTR in ++#define OUTATTR out ++#define highp ++#else ++#define INATTR attribute ++#define OUTATTR varying ++#endif ++uniform mat4 viewProjectionMatrix; ++INATTR highp vec3 vertexPosition; ++INATTR highp vec2 texturePosition; ++OUTATTR highp vec4 textureCoordinates; ++void main() ++{ ++ textureCoordinates = vec4(texturePosition.x, texturePosition.y, 0.0, 1.0); ++ gl_Position = viewProjectionMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0); ++} +diff --git a/tests/manual/hdr-qopenglwidget/main.cpp b/tests/manual/hdr-qopenglwidget/main.cpp +new file mode 100644 +index 0000000000..e517ef8579 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/main.cpp +@@ -0,0 +1,153 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2016 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include ++#include "window.h" ++ ++#include "openglprobeutils.h" ++#include ++ ++QSurfaceFormat generateSurfaceFormat(QSurfaceFormat::RenderableType renderer, ++ QSurfaceFormat::ColorSpace colorSpace, ++ int bitDepth) ++{ ++ QSurfaceFormat format; ++#ifdef Q_OS_MACOS ++ format.setVersion(3, 2); ++ format.setProfile(QSurfaceFormat::CoreProfile); ++#else ++ format.setVersion(3, 0); ++ format.setProfile(QSurfaceFormat::CoreProfile); ++#endif ++ format.setDepthBufferSize(24); ++ format.setStencilBufferSize(8); ++ ++ switch (bitDepth) { ++ case 8: ++ format.setRedBufferSize(8); ++ format.setGreenBufferSize(8); ++ format.setBlueBufferSize(8); ++ format.setAlphaBufferSize(8); ++ break; ++ case 10: ++ format.setRedBufferSize(10); ++ format.setGreenBufferSize(10); ++ format.setBlueBufferSize(10); ++ format.setAlphaBufferSize(2); ++ break; ++ case 16: ++ format.setRedBufferSize(16); ++ format.setGreenBufferSize(16); ++ format.setBlueBufferSize(16); ++ format.setAlphaBufferSize(16); ++ break; ++ default: ++ qFatal("Unsupported surface bit depth %d", bitDepth); ++ } ++ ++ format.setRenderableType(renderer); ++ format.setColorSpace(colorSpace); ++ ++ format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); ++ format.setSwapInterval(0); // Disable vertical refresh syncing ++ ++ return format; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ QVector allFormats; ++ ++ QVector availableRenderers; ++ availableRenderers << QSurfaceFormat::OpenGL; ++ availableRenderers << QSurfaceFormat::OpenGLES; ++ ++ for (QSurfaceFormat::RenderableType renderer : availableRenderers) { ++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 8); ++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 8); ++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 10); ++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 10); ++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::scRGBColorSpace, 16); ++ } ++ ++ for (QSurfaceFormat format : allFormats) { ++ qDebug() << "Probing: " << format; ++ bool result = OpenGLProbeUtils::probeFormat(format, true); ++ qDebug() << " result:" << result; ++ } ++ ++ ++ if (argc > 1 && !strcmp(argv[1], "--sharecontext")) { ++ qDebug("Requesting all contexts to share"); ++ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); ++ } ++ ++ QApplication a(argc, argv); ++ ++ QSurfaceFormat::RenderableType renderer = QSurfaceFormat::OpenGLES; ++ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::scRGBColorSpace; ++ int bitDepth = 16; ++ ++ ++ if (QCoreApplication::arguments().contains(QLatin1String("--scrgb"))) { ++ colorSpace = QSurfaceFormat::scRGBColorSpace; ++ bitDepth = 16; ++ } else if (QCoreApplication::arguments().contains(QLatin1String("--bt2020pq"))) { ++ colorSpace = QSurfaceFormat::bt2020PQColorSpace; ++ bitDepth = 10; ++ } else if (QCoreApplication::arguments().contains(QLatin1String("--srgb"))) { ++ colorSpace = QSurfaceFormat::sRGBColorSpace; ++ bitDepth = 8; ++ } ++ ++ if (QCoreApplication::arguments().contains(QLatin1String("--opengl"))) { ++ renderer = QSurfaceFormat::OpenGL; ++ } else if (QCoreApplication::arguments().contains(QLatin1String("--opengles"))) { ++ renderer = QSurfaceFormat::OpenGLES; ++ } ++ ++ QSurfaceFormat format = generateSurfaceFormat(renderer, colorSpace, bitDepth); ++ ++ if (QCoreApplication::arguments().contains(QLatin1String("--multisample"))) { ++ format.setSamples(4); ++ } ++ ++ if (format.renderableType() == QSurfaceFormat::OpenGL) { ++ QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); ++ } else if (format.renderableType() == QSurfaceFormat::OpenGLES) { ++ QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); ++ } ++ ++ qDebug() << "Requesting" << format.renderableType() << format; ++ QSurfaceFormat::setDefaultFormat(format); ++ ++ Window window; ++ window.show(); ++ ++ return a.exec(); ++} +diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp +new file mode 100644 +index 0000000000..687cc08904 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp +@@ -0,0 +1,139 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "openglprobeutils.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++namespace OpenGLProbeUtils { ++ ++namespace { ++ ++struct AppAttributeSetter ++{ ++ AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES) ++ : m_attribute(attribute), ++ m_oldValue(QCoreApplication::testAttribute(attribute)) ++ { ++ QCoreApplication::setAttribute(attribute, useOpenGLES); ++ } ++ ++ ~AppAttributeSetter() { ++ QCoreApplication::setAttribute(m_attribute, m_oldValue); ++ } ++ ++private: ++ Qt::ApplicationAttribute m_attribute; ++ bool m_oldValue = false; ++}; ++ ++struct SurfaceFormatSetter ++{ ++ SurfaceFormatSetter(const QSurfaceFormat &format) ++ : m_oldFormat(QSurfaceFormat::defaultFormat()) ++ { ++ QSurfaceFormat::setDefaultFormat(format); ++ } ++ ++ ~SurfaceFormatSetter() { ++ QSurfaceFormat::setDefaultFormat(m_oldFormat); ++ } ++ ++private: ++ QSurfaceFormat m_oldFormat; ++}; ++ ++} ++ ++bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs) ++{ ++ return lhs == rhs || ++ ((lhs == QSurfaceFormat::DefaultColorSpace || ++ lhs == QSurfaceFormat::sRGBColorSpace) && ++ (rhs == QSurfaceFormat::DefaultColorSpace || ++ rhs == QSurfaceFormat::sRGBColorSpace)); ++} ++ ++bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState) ++{ ++ QScopedPointer sharedContextSetter; ++ QScopedPointer glSetter; ++ QScopedPointer glesSetter; ++ QScopedPointer formatSetter; ++ QScopedPointer application; ++ ++ if (adjustGlobalState) { ++ sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false)); ++ ++ if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) { ++ glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES)); ++ glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES)); ++ } ++ ++ formatSetter.reset(new SurfaceFormatSetter(format)); ++ ++ int argc = 1; ++ QByteArray data("krita"); ++ char *argv = data.data(); ++ application.reset(new QApplication(argc, &argv)); ++ } ++ ++ QWindow surface; ++ surface.setFormat(format); ++ surface.setSurfaceType(QSurface::OpenGLSurface); ++ surface.create(); ++ QOpenGLContext context; ++ context.setFormat(format); ++ ++ ++ if (!context.create()) { ++ qCritical() << "OpenGL context cannot be created"; ++ return false; ++ } ++ if (!context.isValid()) { ++ qCritical() << "OpenGL context is not valid while checking Qt's OpenGL status"; ++ return false; ++ } ++ if (!context.makeCurrent(&surface)) { ++ qCritical() << "OpenGL context cannot be made current"; ++ return false; ++ } ++ ++ if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) { ++ qCritical() << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace(); ++ return false; ++ } ++ ++ return true; ++} ++ ++} +diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.h b/tests/manual/hdr-qopenglwidget/openglprobeutils.h +new file mode 100644 +index 0000000000..3b2f1ec3d0 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.h +@@ -0,0 +1,42 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#ifndef OPENGLPROBEUTILS_H ++#define OPENGLPROBEUTILS_H ++ ++#include ++ ++namespace OpenGLProbeUtils ++{ ++ ++bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs); ++bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState); ++ ++}; ++ ++#endif // OPENGLPROBEUTILS_H +diff --git a/tests/manual/hdr-qopenglwidget/window.cpp b/tests/manual/hdr-qopenglwidget/window.cpp +new file mode 100644 +index 0000000000..5729660a4f +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/window.cpp +@@ -0,0 +1,219 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "window.h" ++ ++#include "KisGLImageWidget.h" ++#include "KisGLImageF16.h" ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++ ++Window::Window() ++{ ++ setWindowTitle("16 bit float QOpenGLWidget test"); ++ QMenu *menu = menuBar()->addMenu("File"); ++ QToolBar *tb = addToolBar("File"); ++ ++ m_quitAction = new QAction("Quit", this); ++ connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close())); ++ menu->addAction(m_quitAction); ++ tb->addAction(m_quitAction); ++ ++ QWidget *centralWidget = new QWidget(this); ++ QVBoxLayout *layout = new QVBoxLayout(centralWidget); ++ ++ QHBoxLayout *hLayout = new QHBoxLayout(centralWidget); ++ ++ m_imageWidget = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this); ++ m_imageWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ++ hLayout->addWidget(m_imageWidget, 0, Qt::AlignLeft); ++ ++ m_imageWidgetSdr = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this); ++ m_imageWidgetSdr->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ++ hLayout->addWidget(m_imageWidgetSdr, 0, Qt::AlignLeft); ++ ++ QImage image(QSize(255,255), QImage::Format_ARGB32); ++ image.fill(Qt::red); ++ ++ QLabel *label = new QLabel(this); ++ label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ++ hLayout->addWidget(label, 0, Qt::AlignLeft); ++ ++ m_imageWidget->loadImage(initializeImage(false)); ++ m_imageWidgetSdr->loadImage(initializeImage(true)); ++ label->setPixmap(QPixmap::fromImage(convertToQImage(m_imageWidget->image()))); ++ ++ layout->addLayout(hLayout); ++ ++ m_lblContextInfo = new QLabel(this); ++ layout->addWidget(m_lblContextInfo); ++ ++ QLabel *lblNotes = new QLabel(this); ++ lblNotes->setWordWrap(true); ++ lblNotes->setText("* In SDR display mode all three images should look exactly the same\n" ++ "* In HDR display mode: image 1 should look brighter than the others " ++ "(it is HDR), images 2 and 3 should have exactly the same brightness and look"); ++ ++ layout->addWidget(lblNotes); ++ ++ centralWidget->setLayout(layout); ++ setCentralWidget(centralWidget); ++} ++ ++inline qfloat16 floatToFloat16(float x) { ++ qfloat16 result; ++ qFloatToFloat16(&result, &x, 1); ++ return result; ++} ++ ++inline float float16ToFloat(qfloat16 x) { ++ float result; ++ qFloatFromFloat16(&result, &x, 1); ++ return result; ++} ++ ++ ++KisGLImageF16 Window::initializeImage(bool cropRange) const ++{ ++ const int size = 256; ++ KisGLImageF16 image(size, size); ++ image.clearPixels(); ++ qfloat16 *pixelPtr = image.data(); ++ ++ for (int y = 0; y < size; y++) { ++ for (int x = 0; x < size; x++) { ++ qfloat16 *pxl = reinterpret_cast(pixelPtr); ++ ++ float hdrRedValue = 25.0f * std::pow(float(x) / size, 5.0f); ++ ++ if (cropRange) { ++ hdrRedValue = qMin(hdrRedValue, 1.0f); ++ } ++ ++ pxl[0] = floatToFloat16(hdrRedValue); ++ ++ if (y > size / 2) { ++ const float portion = (float(y) / size - 0.5f) * 2.0f; ++ const float value = qMin(1.0f, 0.2f + 1.8f * portion); ++ ++ pxl[1] = floatToFloat16(value); ++ pxl[2] = floatToFloat16(value); ++ } else { ++ pxl[1] = floatToFloat16(0.2); ++ pxl[2] = floatToFloat16(0.2); ++ } ++ ++ pxl[3] = floatToFloat16(1.0); ++ ++ pixelPtr += 4; ++ } ++ } ++ ++ return image; ++} ++ ++inline float linearToSRGB(float value) ++{ ++ if (value <= 0.0f) { ++ value = 0.0f; ++ } else if (value < 0.0031308f) { ++ value = value * 12.92f; ++ } else if (value < 1.0f) { ++ value = std::pow(value, 0.41666f) * 1.055f - 0.055f; ++ } else { ++ value = 1.0f; ++ } ++ return value; ++} ++ ++QImage Window::convertToQImage(const KisGLImageF16 &image) const ++{ ++ const QSize size = image.size(); ++ const qfloat16 *pixelPtr = image.constData(); ++ ++ QImage qimage(size, QImage::Format_ARGB32); ++ quint8 *qimagePixelPtr = qimage.bits(); ++ ++ ++ for (int y = 0; y < size.height(); y++) { ++ for (int x = 0; x < size.width(); x++) { ++ const qfloat16 *srcPxl = pixelPtr; ++ quint8 *dstPxl = qimagePixelPtr; ++ ++ auto convertChannel = [] (qfloat16 x) { ++ float value = float16ToFloat(x); ++ return quint8(linearToSRGB(value) * 255.0f); ++ }; ++ ++ dstPxl[0] = convertChannel(srcPxl[2]); ++ dstPxl[1] = convertChannel(srcPxl[1]); ++ dstPxl[2] = convertChannel(srcPxl[0]); ++ dstPxl[3] = convertChannel(srcPxl[3]); ++ ++ pixelPtr += 4; ++ qimagePixelPtr += 4; ++ } ++ } ++ ++ return qimage; ++} ++ ++void Window::updateSurfaceInfo() ++{ ++ const QSurfaceFormat format = m_imageWidget->context()->format(); ++ ++ m_lblContextInfo->setText( ++ QString("renderer: %1\ncolorSpace: %2\n\n") ++ .arg(format.renderableType() == QSurfaceFormat::OpenGL ? "openGL" : "openGL ES") ++ .arg(format.colorSpace() == QSurfaceFormat::sRGBColorSpace ? "sRGB" : ++ format.colorSpace() == QSurfaceFormat::scRGBColorSpace ? "scRGB" : ++ format.colorSpace() == QSurfaceFormat::bt2020PQColorSpace ? "Bt. 2020 PQ" : ++ "unknown")); ++} ++ ++void Window::showEvent(QShowEvent *ev) ++{ ++ QMainWindow::showEvent(ev); ++ ++ if (m_lblContextInfo->text().isEmpty()) { ++ updateSurfaceInfo(); ++ } ++} +diff --git a/tests/manual/hdr-qopenglwidget/window.h b/tests/manual/hdr-qopenglwidget/window.h +new file mode 100644 +index 0000000000..fd8e5c0393 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/window.h +@@ -0,0 +1,69 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#ifndef WINDOW_H ++#define WINDOW_H ++ ++#include ++ ++class QAction; ++ ++class GLWidget; ++class KisGLImageWidget; ++class KisGLImageF16; ++class QLabel; ++ ++class Window : public QMainWindow ++{ ++ Q_OBJECT ++ ++public: ++ Window(); ++ ++ void showEvent(QShowEvent *ev) override; ++ ++public Q_SLOTS: ++ ++ ++private: ++ KisGLImageF16 initializeImage(bool cropRange) const; ++ QImage convertToQImage(const KisGLImageF16 &image) const; ++ ++ void updateSurfaceInfo(); ++ ++private: ++ GLWidget *m_glWidget {0}; ++ QAction *m_openAction {0}; ++ QAction *m_quitAction {0}; ++ KisGLImageWidget *m_imageWidget; ++ KisGLImageWidget *m_imageWidgetSdr; ++ QLabel *m_lblContextInfo; ++ ++}; ++ ++#endif +diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro +index ab00a5ef60..b202ed0431 100644 +--- a/tests/manual/manual.pro ++++ b/tests/manual/manual.pro +@@ -59,7 +59,7 @@ qtabbar + + qtConfig(opengl) { + SUBDIRS += qopengltextureblitter +- qtConfig(egl): SUBDIRS += qopenglcontext ++ qtConfig(egl): SUBDIRS += qopenglcontext hdr-qopenglwidget + } + + win32: SUBDIRS -= network_remote_stresstest network_stresstest +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch b/3rdparty/ext_qt/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch new file mode 100644 index 0000000000..d51572c13f --- /dev/null +++ b/3rdparty/ext_qt/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch @@ -0,0 +1,29 @@ +From d06dc5de61c3574dd97c5aaae07561d8fc29c604 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Thu, 6 Dec 2018 16:16:27 +0300 +Subject: [PATCH 08/17] Fix notification of QDockWidget when it gets undocked + +Before the patch the notification was emitted only when the docker +was attached to the panel or changed a position on it. + +Change-Id: Id3ffbd2018a8e68844d174328dd1c4ceb7fa01d3 +--- + src/widgets/widgets/qdockwidget.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp +index 6c871aae2c..19fc2d1677 100644 +--- a/src/widgets/widgets/qdockwidget.cpp ++++ b/src/widgets/widgets/qdockwidget.cpp +@@ -1171,6 +1171,8 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect + QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q); + if (mwlayout) + emit q->dockLocationChanged(mwlayout->dockWidgetArea(q)); ++ } else { ++ emit q->dockLocationChanged(Qt::NoDockWidgetArea); + } + } + +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0010-Fix-tablet-jitter-on-X11.patch b/3rdparty/ext_qt/0010-Fix-tablet-jitter-on-X11.patch deleted file mode 100644 index 799c1eee3c..0000000000 --- a/3rdparty/ext_qt/0010-Fix-tablet-jitter-on-X11.patch +++ /dev/null @@ -1,68 +0,0 @@ -From a05a69e789006bbf3fbfdd1998bc58af7b826b4b Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Sun, 10 Mar 2019 14:48:58 +0300 -Subject: [PATCH] Fix tablet jitter on X11 - -We should get the correct stylus position from the valuators, -not from the X11-provided global position. Global position is rounded -to the nearest FP16 values, which is not enough for smooth painting. - -Change-Id: Ie701446b3586296bcb8fb09158f387ba6a7cbf07 ---- - src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 26 ++++++++++++++++++++++++ - 1 file changed, 26 insertions(+) - -diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp -index 04ddd3c..20aca33 100644 ---- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp -+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp -@@ -1208,6 +1208,11 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD - return handled; - } - -+inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize) -+{ -+ return screenMin + normValue * screenSize; -+} -+ - void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData) - { - auto *ev = reinterpret_cast(event); -@@ -1221,6 +1226,15 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD - double pressure = 0, rotation = 0, tangentialPressure = 0; - int xTilt = 0, yTilt = 0; - -+ // Valuators' values are relative to the physical size of the current virtual -+ // screen. Therefore we cannot use QScreen/QWindow geometry and should use -+ // QPlatformWindow/QPlatformScreen instead. -+ QRect physicalScreenArea; -+ const QList siblings = window->screen()->handle()->virtualSiblings(); -+ for (const QPlatformScreen *screen : siblings) { -+ physicalScreenArea |= screen->geometry(); -+ } -+ - for (QHash::iterator it = tabletData->valuatorInfo.begin(), - ite = tabletData->valuatorInfo.end(); it != ite; ++it) { - int valuator = it.key(); -@@ -1228,6 +1242,18 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD - xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal); - double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal); - switch (valuator) { -+ case QXcbAtom::AbsX: { -+ const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width()); -+ global.setX(value); -+ local.setX(value - window->handle()->geometry().x()); -+ break; -+ } -+ case QXcbAtom::AbsY: { -+ qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height()); -+ global.setY(value); -+ local.setY(value - window->handle()->geometry().y()); -+ break; -+ } - case QXcbAtom::AbsPressure: - pressure = normalizedValue; - break; --- -2.7.4 - diff --git a/3rdparty/ext_qt/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch b/3rdparty/ext_qt/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch deleted file mode 100644 index e304f7fff1..0000000000 --- a/3rdparty/ext_qt/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 8b82ab988807cd424e2e54b29dea4cde8e687fab Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Sun, 10 Mar 2019 14:51:28 +0300 -Subject: [PATCH] Add an ID for recognition of UGEE tablets - -Change-Id: I2228ee9d53aa23a2d2cd9970a363d8424e744093 ---- - src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp -index 20aca33..411366f 100644 ---- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp -+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp -@@ -240,6 +240,10 @@ void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting) - } else if (name.contains("uc-logic") && isTablet) { - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); -+ } else if (name.contains("ugee")) { -+ isTablet = true; -+ tabletData.pointerType = QTabletEvent::Pen; -+ dbgType = QLatin1String("pen"); - } else { - isTablet = false; - } --- -2.7.4 - diff --git a/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch b/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch index b5db94adc3..644c0cd236 100644 --- a/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch +++ b/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch @@ -1,39 +1,39 @@ -From a36e3651fe79bbbdea44d78e11a97705687cffa9 Mon Sep 17 00:00:00 2001 +From 0ae8de8731d39fd1e736c7d9012f87184299956e Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Mar 2019 13:18:06 +0300 -Subject: [PATCH] Synthesize Enter/LeaveEvent for accepted QTabletEvent +Subject: [PATCH 1/2] Synthesize Enter/LeaveEvent for accepted QTabletEvent When the tablet event is accepted, then Qt doesn't synthesize a mouse event, it means that QApplicationPrivate::sendMouseEvent() will not be called, and, therefore, enter/leave events will not be dispatched. The patch looks a bit hackish. Ideally, the synthesize should happen in QGuiApplicationPrivate::processTabletEvent(), which takes the decision about synthesizing mouse events. But there is not enough information on this level: neither qt_last_mouse_receiver nor the receiver widget are known at this stage. Change-Id: Ifbad6284483ee282ad129db54606f5d0d9ddd633 --- src/widgets/kernel/qwidgetwindow.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp -index e9b749d..983ab77 100644 +index fbc71cd0ea..25cb486915 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp -@@ -1053,6 +1053,11 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event) +@@ -1051,6 +1051,11 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event) event->setAccepted(ev.isAccepted()); } + if (event->isAccepted() && widget != qt_last_mouse_receiver) { + QApplicationPrivate::dispatchEnterLeave(widget, qt_last_mouse_receiver, event->globalPos()); + qt_last_mouse_receiver = widget; + } + if (event->type() == QEvent::TabletRelease && event->buttons() == Qt::NoButton) qt_tablet_target = 0; } -- -2.7.4 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch b/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch index 641697c9b6..dd39f1b369 100644 --- a/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch +++ b/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch @@ -1,28 +1,28 @@ -From 6851994441cc48cf48853f009dd620e9cebd551a Mon Sep 17 00:00:00 2001 +From 82c789f75fc634ccbe5f1aad6d41895a6e6da17d Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Mar 2019 16:17:17 +0300 -Subject: [PATCH] Poison Qt's headers with a mark about presence of Enter/Leave - patch +Subject: [PATCH 2/2] Poison Qt's headers with a mark about presence of + Enter/Leave patch --- src/gui/kernel/qevent.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/kernel/qevent.h b/src/gui/kernel/qevent.h -index 2b1c6a6..69cafaa 100644 +index 2b1c6a6e31..69cafaaa29 100644 --- a/src/gui/kernel/qevent.h +++ b/src/gui/kernel/qevent.h @@ -242,6 +242,10 @@ protected: }; #endif +// a temporary mark to know if the patch has landed to Qt or not +// https://codereview.qt-project.org/#/c/255384/ +#define QT_HAS_ENTER_LEAVE_PATCH + #if QT_CONFIG(tabletevent) class Q_GUI_EXPORT QTabletEvent : public QInputEvent { -- -2.7.4 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch b/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch index d03ea62dd7..e9e0664c1e 100644 --- a/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch +++ b/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch @@ -1,50 +1,50 @@ -From 37be272df1fac2bc4633fbddafd3662d1f0921b9 Mon Sep 17 00:00:00 2001 +From 89b0f14299deac2ad0802f3addfbf487ba680720 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Mar 2019 13:18:06 +0300 -Subject: [PATCH 10/27] Synthesize Enter/LeaveEvent for accepted QTabletEvent +Subject: [PATCH 09/17] Synthesize Enter/LeaveEvent for accepted QTabletEvent When the tablet event is accepted, then Qt doesn't synthesize a mouse event, it means that QApplicationPrivate::sendMouseEvent() will not be called, and, therefore, enter/leave events will not be dispatched. The patch looks a bit hackish. Ideally, the synthesize should happen in QGuiApplicationPrivate::processTabletEvent(), which takes the decision about synthesizing mouse events. But there is not enough information on this level: neither qt_last_mouse_receiver nor the receiver widget are known at this stage. On Windows and other platforms where there is a parallel stream of mouse events synthesized by the platform, we shouldn't generate these events manually. Change-Id: Ifbad6284483ee282ad129db54606f5d0d9ddd633 --- src/widgets/kernel/qwidgetwindow.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp -index fbc71cd0..729a7f70 100644 +index fbc71cd0ea..729a7f701a 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -1051,6 +1051,18 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event) event->setAccepted(ev.isAccepted()); } + /** + * Synthesize Enter/Leave events if it is requested by the system and user + */ + if (widget != qt_last_mouse_receiver && + event->isAccepted() && + !QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse && + qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)) { + + QApplicationPrivate::dispatchEnterLeave(widget, qt_last_mouse_receiver, event->globalPos()); + qt_last_mouse_receiver = widget; + } + if (event->type() == QEvent::TabletRelease && event->buttons() == Qt::NoButton) qt_tablet_target = 0; } -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch b/3rdparty/ext_qt/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch deleted file mode 100644 index 61d03a8cfd..0000000000 --- a/3rdparty/ext_qt/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch +++ /dev/null @@ -1,40 +0,0 @@ -From cd8b94f7d7b47e2b4c1181a2cd2911937ef344a6 Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Sat, 30 Mar 2019 23:14:07 +0300 -Subject: [PATCH 11/27] Fix generation of Leave events when using tablet - devices - -When both mouse and tablet events are handled by QWindowsPointerHandler, -m_currentWindow variable is shared among the two event streams, therefore -each stream should ensure it does euqivalent operations, when changing it. - -Here we should subscribe to the Leave events, when we emit Enter event -from the inside of the tablet events flow. Without whis subscription, -the cursor may stuck into "resize" state when crossing the window's -frame multiple times. - -Change-Id: I88df4a42ae86243e10ecd4a4cedf87639c96d169 ---- - src/plugins/platforms/windows/qwindowspointerhandler.cpp | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -index 9a8b5d51..a83289de 100644 ---- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp -+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -@@ -614,7 +614,12 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin - if (m_needsEnterOnPointerUpdate) { - m_needsEnterOnPointerUpdate = false; - if (window != m_currentWindow) { -+ -+ // make sure we subscribe to leave events for this window -+ trackLeave(hwnd); -+ - QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos); -+ - m_currentWindow = window; - if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target)) - wumPlatformWindow->applyCursor(); --- -2.22.0.windows.1 - diff --git a/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch b/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch index 11bfb7deb7..3bcbb7e59e 100644 --- a/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch +++ b/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch @@ -1,73 +1,73 @@ -From 52dd12d4e4eeb64864fe5708e2a3fb775c20c743 Mon Sep 17 00:00:00 2001 +From d95b6e9a4029a46acbe5df4b068de8a646887430 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 3 Apr 2019 18:37:56 +0300 -Subject: [PATCH 12/27] Implement a switch for tablet API on Windows +Subject: [PATCH 10/17] Implement a switch for tablet API on Windows Qt has support for two tablet APIs: WinTab and Windows Pointer API. The former one is used in professional graphical tablet devices, like Wacom, Huion and etc. The latter is mostly used in two-in-one convertible laptops, like Surface Pro. By default Qt prefers Windows Pointer API, if it is available. The problem is that some devices (e.g. Huion tablets) do not support Windows Pointer API. More than that, even devices, which support Pointer API, must limit their capabilities to fit it: 1) Winodws Pointer API doesn't support more than one stylus barrel buttons, but all professional devices have at least two buttons. 2) Winodws Pointer API limits pressure resolution to 1024 levels, but even entry-level Wacom devices have at least 2048 levels. Professional-level devices have 4096 levels. Therefore painting applications should be able to choose, which API they prefer. This patch implements a special application attribute Qt::AA_MSWindowsUseWinTabAPI. Application should set it before creation of QApplication to force selection of WinTab API. When running, application can check currently running API by testing this attribute. --- src/corelib/global/qnamespace.h | 1 + src/plugins/platforms/windows/qwindowsintegration.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h -index dec2c446..3ab99219 100644 +index dec2c44637..3ab9921986 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -525,6 +525,7 @@ public: AA_DontShowShortcutsInContextMenus = 28, AA_CompressTabletEvents = 29, AA_DisableWindowContextHelpButton = 30, // ### Qt 6: remove me + AA_MSWindowsUseWinTabAPI = 31, // Win only // Add new attributes before this line AA_AttributeCount diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp -index 2c90b048..c415cf28 100644 +index 5c1fa00088..d2d12ff7e5 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -236,10 +236,16 @@ QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(const QStringList ¶mL m_options = parseOptions(paramList, &tabletAbsoluteRange, &dpiAwareness); QWindowsFontDatabase::setFontOptions(m_options); + if (QCoreApplication::testAttribute(Qt::AA_MSWindowsUseWinTabAPI)) { + m_options |= QWindowsIntegration::DontUseWMPointer; + } + if (m_context.initPointer(m_options)) { QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); } else { - m_context.initTablet(m_options); + if (m_context.initTablet(m_options)) + QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); + if (tabletAbsoluteRange >= 0) m_context.setTabletAbsoluteRange(tabletAbsoluteRange); } -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch b/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch index d67726efc5..2cbec63601 100644 --- a/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch +++ b/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch @@ -1,137 +1,139 @@ -From 7d4d465771854cfa860b7cd2eb09f91cfdb8b5cd Mon Sep 17 00:00:00 2001 +From 8485c169f76c074256616a55308c1a57174ae064 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 18:08:33 +0300 -Subject: [PATCH 13/27] Fetch stylus button remapping from WinTab driver +Subject: [PATCH 11/17] Fetch stylus button remapping from WinTab driver The user can remap the stylus buttons using tablet driver settings. This information is available to the application via CSR_SYSBTNMAP WinTab feature. We should fetch this information every time the stylus gets into proximity, because the user can change these settings on the fly. + +Change-Id: Idc839905c3485179d782814f78fa862fd4a99127 --- .../windows/qwindowstabletsupport.cpp | 72 ++++++++++++++++++- .../platforms/windows/qwindowstabletsupport.h | 2 + 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index fa209f09..31655101 100644 +index fa209f09c4..44b94d044d 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -435,6 +435,27 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L m_currentDevice = m_devices.size(); m_devices.push_back(tabletInit(uniqueId, cursorType)); } + + /** + * We should check button map for changes on every proximity event, not + * only during initialization phase. + * + * WARNING: in 2016 there were some Wacom table drivers, which could mess up + * button mapping if the remapped button was pressed, while the + * application **didn't have input focus**. This bug is somehow + * related to the fact that Wacom drivers allow user to configure + * per-application button-mappings. If the bug shows up again, + * just move this button-map fetching into initialization block. + * + * See https://bugs.kde.org/show_bug.cgi?id=359561 + */ + BYTE logicalButtons[32]; + memset(logicalButtons, 0, 32); + m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_SYSBTNMAP, &logicalButtons); + m_devices[m_currentDevice].buttonsMap[0x1] = logicalButtons[0]; + m_devices[m_currentDevice].buttonsMap[0x2] = logicalButtons[1]; + m_devices[m_currentDevice].buttonsMap[0x4] = logicalButtons[2]; + m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor); m_state = PenProximity; qCDebug(lcQpaTablet) << "enter proximity for device #" -@@ -446,6 +467,50 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L +@@ -446,6 +467,52 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L return true; } +Qt::MouseButton buttonValueToEnum(DWORD button, + const QWindowsTabletDeviceData &tdd) { -+ const int leftButtonValue = 0x1; -+ const int middleButtonValue = 0x2; -+ const int rightButtonValue = 0x4; -+ const int doubleClickButtonValue = 0x7; ++ ++ enum : unsigned { ++ leftButtonValue = 0x1, ++ middleButtonValue = 0x2, ++ rightButtonValue = 0x4, ++ doubleClickButtonValue = 0x7 ++ }; + + button = tdd.buttonsMap.value(button); + + return button == leftButtonValue ? Qt::LeftButton : + button == rightButtonValue ? Qt::RightButton : + button == doubleClickButtonValue ? Qt::MiddleButton : + button == middleButtonValue ? Qt::MiddleButton : + button ? Qt::LeftButton /* fallback item */ : + Qt::NoButton; +} + -+void convertTabletButtons(DWORD btnNew, -+ Qt::MouseButtons *buttons, -+ const QWindowsTabletDeviceData &tdd, -+ const Qt::KeyboardModifiers keyboardModifiers) { ++Qt::MouseButtons convertTabletButtons(DWORD btnNew, ++ const QWindowsTabletDeviceData &tdd) { + -+ *buttons = Qt::NoButton; -+ for (int i = 0; i < 3; i++) { -+ int btn = 0x1 << i; ++ Qt::MouseButtons buttons = Qt::NoButton; ++ for (unsigned int i = 0; i < 3; i++) { ++ unsigned int btn = 0x1 << i; + + if (btn & btnNew) { + Qt::MouseButton convertedButton = + buttonValueToEnum(btn, tdd); + -+ *buttons |= convertedButton; ++ buttons |= convertedButton; + + /** + * If a button that is present in hardware input is + * mapped to a Qt::NoButton, it means that it is going + * to be eaten by the driver, for example by its + * "Pan/Scroll" feature. Therefore we shouldn't handle + * any of the events associated to it. We'll just return + * Qt::NoButtons here. + */ + } + } ++ return buttons; +} + bool QWindowsTabletSupport::translateTabletPacketEvent() { static PACKET localPacketBuf[TabletPacketQSize]; // our own tablet packet queue. -@@ -552,9 +617,14 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() +@@ -552,9 +619,12 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation; } -+ Qt::MouseButtons buttons; -+ convertTabletButtons(packet.pkButtons, &buttons, -+ m_devices.at(m_currentDevice), -+ keyboardModifiers); ++ Qt::MouseButtons buttons = ++ convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice)); + QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF, currentDevice, currentPointer, - static_cast(packet.pkButtons), + buttons, pressureNew, tiltX, tiltY, tangentialPressure, rotation, z, uniqueId, diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.h b/src/plugins/platforms/windows/qwindowstabletsupport.h -index d91701d6..5b1ddb52 100644 +index d91701d6a5..8f97982308 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.h +++ b/src/plugins/platforms/windows/qwindowstabletsupport.h @@ -45,6 +45,7 @@ #include #include -+#include ++#include #include @@ -100,6 +101,7 @@ struct QWindowsTabletDeviceData qint64 uniqueId = 0; int currentDevice = 0; int currentPointerType = 0; -+ QMap buttonsMap; ++ QHash buttonsMap; }; #ifndef QT_NO_DEBUG_STREAM -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch b/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch index c623671c51..632429d3cb 100644 --- a/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch +++ b/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch @@ -1,28 +1,28 @@ -From 7c66490d5579828540c410275c0f8e8d72dae3f5 Mon Sep 17 00:00:00 2001 +From c5ee9030553cc37d92391b6f60e978eb27b15768 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 20:29:14 +0300 -Subject: [PATCH 14/27] Disable tablet relative mode in Qt +Subject: [PATCH 12/17] Disable tablet relative mode in Qt --- src/plugins/platforms/windows/qwindowstabletsupport.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index 31655101..3e35b146 100644 +index 44b94d044d..6a9fe28e75 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -@@ -562,6 +562,11 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() +@@ -564,6 +564,11 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() m_state = PenDown; m_mode = (mouseLocation - globalPosF).manhattanLength() > m_absoluteRange ? MouseMode : PenMode; + + // Krita doesn't support mouse mode. And this code may break + // normal painting, so we just disable it. + m_mode = PenMode; + qCDebug(lcQpaTablet) << __FUNCTION__ << "mode=" << m_mode << "pen:" << globalPosF << "mouse:" << mouseLocation; } -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch b/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch index ad27e93407..1f053612c9 100644 --- a/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch +++ b/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch @@ -1,217 +1,219 @@ -From b33666c064dc255e3d39a0646684445ab907d299 Mon Sep 17 00:00:00 2001 +From 71466f90b0b554987a60e5c797d38d6c28f8ebef Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 23:24:01 +0300 -Subject: [PATCH 15/27] Fetch mapped screen size from the Wintab driver +Subject: [PATCH 13/17] Fetch mapped screen size from the Wintab driver Some devices, like Microsoft Surface Pro 5, don't map tablet's input range to the entire virtual screen area, but map it to the primary display that has actual built-in tablet sensor. In such cases we should fetch actualy mapped aread from Wintab's lcSys{Org,Ext}{X,Y} fields and use it for cursor mapping. If one wants to fall back to the old screen size detection method, then an environment variable can be set: QT_IGNORE_WINTAB_MAPPING=1 When the variable is set, the scaling is done via virtual desktop area only. If the tablet driver is broken (e.g. Microsoft SP5, when primary display is set to an external monitor) the user might want to override mapping completely. Then the following variable can be used: QT_WINTAB_DESKTOP_RECT=x;y;width;height + +Change-Id: Idd8bcf0323ce0811d2ad8976eaed48ad13ac3af8 --- .../windows/qwindowstabletsupport.cpp | 89 ++++++++++++++++++- .../platforms/windows/qwindowstabletsupport.h | 13 ++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index 3e35b146..11e6769c 100644 +index 6a9fe28e75..bfd0a9640b 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -216,6 +217,10 @@ QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context) // Some tablets don't support tilt, check if it is possible, if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation)) m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution; + + connect(qGuiApp, &QGuiApplication::primaryScreenChanged, + this, QWindowsTabletSupport::slotPrimaryScreenChanged); + slotScreenGeometryChanged(); } QWindowsTabletSupport::~QWindowsTabletSupport() @@ -394,6 +399,84 @@ QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, UINT return result; } +void QWindowsTabletSupport::slotPrimaryScreenChanged(QScreen *screen) +{ + if (m_connectedScreen) + disconnect(m_connectedScreen, 0, this, 0); + + m_connectedScreen = screen; + + if (m_connectedScreen) + connect(m_connectedScreen, &QScreen::virtualGeometryChanged, + this, &QWindowsTabletSupport::slotScreenGeometryChanged); + + slotScreenGeometryChanged(); +} + +void QWindowsTabletSupport::slotScreenGeometryChanged() +{ + /** + * Some Wintab implementations map the tablet area to the entire + * virtual screen, but others (e.g. Microsoft SP5) don't. They + * may input range to a single (built-in) screen. The logic is + * quite obvious: when the screen has integrated tablet device, + * one cannot map this tablet device to another display. + * + * For such devices, we should always request mapped area from + * lcSys{Org,Ext}{X,Y} fields and use it accordingly. + */ + + LOGCONTEXT lc; + QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lc); + m_wintabScreenGeometry = QRect(lc.lcSysOrgX, lc.lcSysOrgY, lc.lcSysExtX, lc.lcSysExtY); + + qCDebug(lcQpaTablet) << "Updated tablet mapping: " << m_wintabScreenGeometry; + if (QGuiApplication::primaryScreen()) { + qCDebug(lcQpaTablet) << " real desktop geometry: " << QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle()); + } +} + +void QWindowsTabletSupport::updateEffectiveScreenGeometry() +{ + QRect customGeometry; + bool dontUseWintabDesktopRect = false; + + const QString geometry = qEnvironmentVariable("QT_WINTAB_DESKTOP_RECT"); + if (!geometry.isEmpty()) { + QString tmp = QString::fromLatin1("([+-]?\\d+);([+-]?\\d+);(\\d+);(\\d+)"); + + QRegularExpression rex(tmp); + QRegularExpressionMatch match = rex.match(geometry); + + if (match.hasMatch()) { + customGeometry.setRect(match.captured(1).toInt(), + match.captured(2).toInt(), + match.captured(3).toInt(), + match.captured(4).toInt()); + + qCDebug(lcQpaTablet) << "apply QT_WINTAB_DESKTOP_RECT:" << customGeometry; + } else { + qCWarning(lcQpaTablet) << "failed to parse QT_WINTAB_DESKTOP_RECT:" << geometry; + } + } + + if (qEnvironmentVariableIsSet("QT_IGNORE_WINTAB_MAPPING")) { + if (!customGeometry.isValid()) { + qCDebug(lcQpaTablet) << "fallback mapping is requested via QT_IGNORE_WINTAB_MAPPING"; + } else { + qCWarning(lcQpaTablet) << "ignoring QT_IGNORE_WINTAB_MAPPING, because QT_WINTAB_DESKTOP_RECT is set"; + } + dontUseWintabDesktopRect = true; + } + + m_effectiveScreenGeometry = + !customGeometry.isValid() ? + (dontUseWintabDesktopRect ? + QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle()) : + m_wintabScreenGeometry) : + customGeometry; +} + bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam) { PACKET proximityBuffer[1]; // we are only interested in the first packet in this case @@ -421,6 +504,8 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L if (!totalPacks) return false; + updateEffectiveScreenGeometry(); + const UINT currentCursor = proximityBuffer[0].pkCursor; UINT physicalCursorId; QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &physicalCursorId); -@@ -535,8 +620,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() +@@ -537,8 +622,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() // in which case we snap the position to the mouse position. // It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext // area is always the virtual desktop. - const QRect virtualDesktopArea = - QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle()); + + const QRect virtualDesktopArea = m_effectiveScreenGeometry; if (QWindowsContext::verbose > 1) { qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.h b/src/plugins/platforms/windows/qwindowstabletsupport.h -index 5b1ddb52..e8b0c01b 100644 +index 8f97982308..fe7e7815d6 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.h +++ b/src/plugins/platforms/windows/qwindowstabletsupport.h @@ -45,7 +45,9 @@ #include #include +#include - #include + #include +#include #include @@ -56,6 +58,7 @@ QT_BEGIN_NAMESPACE class QDebug; class QWindow; class QRect; +class QScreen; struct QWindowsWinTab32DLL { @@ -108,7 +111,7 @@ struct QWindowsTabletDeviceData QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t); #endif -class QWindowsTabletSupport +class QWindowsTabletSupport : public QObject { Q_DISABLE_COPY(QWindowsTabletSupport) @@ -141,9 +144,14 @@ public: int absoluteRange() const { return m_absoluteRange; } void setAbsoluteRange(int a) { m_absoluteRange = a; } +private Q_SLOTS: + void slotPrimaryScreenChanged(QScreen *screen); + void slotScreenGeometryChanged(); + private: unsigned options() const; QWindowsTabletDeviceData tabletInit(qint64 uniqueId, UINT cursorType) const; + void updateEffectiveScreenGeometry(); static QWindowsWinTab32DLL m_winTab32DLL; const HWND m_window; @@ -154,6 +162,9 @@ private: int m_currentDevice = -1; Mode m_mode = PenMode; State m_state = PenUp; + QScreen *m_connectedScreen = 0; + QRect m_wintabScreenGeometry; + QRect m_effectiveScreenGeometry; }; QT_END_NAMESPACE -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch b/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch index 1e4657f9ca..69c2927f63 100644 --- a/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch +++ b/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch @@ -1,61 +1,79 @@ -From ebfe32b8fe8ff7ce8b8e4330af1191c16b0a8be0 Mon Sep 17 00:00:00 2001 +From dd25106c340d91d20432375a2a22b85779eddb18 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 17 Apr 2019 17:39:10 +0300 -Subject: [PATCH 16/27] Switch stylus pointer type when the tablet is in the +Subject: [PATCH 14/17] Switch stylus pointer type when the tablet is in the tablet proximity Some convertible tablet devices have a special stylus button that converts the stylus into an eraser. Such button can be pressed right when the stylus is in tablet surface proximity, so we should check that not only during proximity event handling, but also while parsing normal wintab packets. +Make sure that we don't switch tablet pointer type while any **mapped** +stylus button is pressed. Pressing the "eraser" button is reported +in pkButtons, but it maps to none by CSR_SYSBTNMAP + https://bugs.kde.org/show_bug.cgi?id=405747 +https://bugs.kde.org/show_bug.cgi?id=408454 --- - .../windows/qwindowstabletsupport.cpp | 23 ++++++++++++++++++- - 1 file changed, 22 insertions(+), 1 deletion(-) + .../windows/qwindowstabletsupport.cpp | 29 ++++++++++++++++--- + 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index 11e6769c..b0431276 100644 +index bfd0a9640b..02455536fe 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -@@ -604,7 +604,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() +@@ -606,7 +606,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() return false; const int currentDevice = m_devices.at(m_currentDevice).currentDevice; - const int currentPointer = m_devices.at(m_currentDevice).currentPointerType; const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId; // The tablet can be used in 2 different modes (reflected in enum Mode), -@@ -634,6 +633,28 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() +@@ -636,6 +635,31 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() for (int i = 0; i < packetCount ; ++i) { const PACKET &packet = localPacketBuf[i]; + int currentPointer = m_devices.at(m_currentDevice).currentPointerType; + + const int packetPointerType = pointerType(packet.pkCursor); -+ if (!packet.pkButtons && packetPointerType != currentPointer) { ++ Qt::MouseButtons buttons = ++ convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice)); ++ ++ if (buttons == Qt::NoButton && packetPointerType != currentPointer) { + + QWindowSystemInterface::handleTabletLeaveProximityEvent(packet.pkTime, + m_devices.at(m_currentDevice).currentDevice, + m_devices.at(m_currentDevice).currentPointerType, + m_devices.at(m_currentDevice).uniqueId); + + + + m_devices[m_currentDevice].currentPointerType = packetPointerType; + + QWindowSystemInterface::handleTabletEnterProximityEvent(packet.pkTime, + m_devices.at(m_currentDevice).currentDevice, + m_devices.at(m_currentDevice).currentPointerType, + m_devices.at(m_currentDevice).uniqueId); + + currentPointer = packetPointerType; + } + const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0; QPointF globalPosF = +@@ -709,9 +733,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() + << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation; + } + +- Qt::MouseButtons buttons = +- convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice)); +- + QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF, + currentDevice, currentPointer, + buttons, -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch index 82bcb7b98f..cda9644e3b 100644 --- a/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch +++ b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch @@ -1,38 +1,40 @@ -From a354a9a7f0cf081aae223dfff3f1734add050b18 Mon Sep 17 00:00:00 2001 +From f83799913665e06e98c743326eafd47d78087477 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Thu, 18 Apr 2019 15:42:17 +0300 -Subject: [PATCH 17/27] Fix updating tablet pressure resolution on every +Subject: [PATCH 15/17] Fix updating tablet pressure resolution on every proximity enter event The user can switch pressure sensitivity level in the driver, which will make our saved values invalid (this option is provided by Wacom drivers for compatibility reasons, and it can be adjusted on the fly) See the bug: https://bugs.kde.org/show_bug.cgi?id=391054 + +Change-Id: I6cfdff27eaf5a587bf714871f1495a7ea150c553 --- src/plugins/platforms/windows/qwindowstabletsupport.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index b0431276..2de87758 100644 +index 02455536fe..e9d10a2b03 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -519,6 +519,14 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L if (m_currentDevice < 0) { m_currentDevice = m_devices.size(); m_devices.push_back(tabletInit(uniqueId, cursorType)); + } else { + /** + * The user can switch pressure sensitivity level in the driver, + * which will make our saved values invalid (this option is + * provided by Wacom drivers for compatibility reasons, and + * it can be adjusted on the fly) + */ + m_devices[m_currentDevice] = tabletInit(uniqueId, cursorType); } /** -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch b/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch index 8902d797de..64d3476751 100644 --- a/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch +++ b/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch @@ -1,52 +1,52 @@ -From f56bfdc8a244fbd82a1901220fac642ca78d8c23 Mon Sep 17 00:00:00 2001 +From 8281d9ff26581797bf489d937824f2142f97b027 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 15 May 2019 19:39:44 +0300 -Subject: [PATCH 18/27] Fix using tablet on QML widgets +Subject: [PATCH 2/3] Fix using tablet on QML widgets In previous versions of Qt (wintab impeplementation) the events were marked by Qt::MouseEventSynthesizedBySystem flag only when they were synthesized from touch, not from tablet events. This is what QWindowsTabletSupport does and what QQuickWindow expects (it filters out all synthesized events). This patch recovers the old behavior for the new QWindowsPointerHandler tablet API implementation. See bug: https://bugs.kde.org/show_bug.cgi?id=406668 --- src/plugins/platforms/windows/qwindowspointerhandler.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -index a83289de..5ba58c24 100644 +index fd3d711470..bf201b87e0 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -@@ -644,14 +644,16 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin +@@ -648,14 +648,16 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin #endif } -static inline bool isMouseEventSynthesizedFromPenOrTouch() +static inline bool isMouseEventSynthesizedFromPen() { // For details, see // https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00; const LONG_PTR MI_WP_SIGNATURE = 0xFF515700; - return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE); + const quint64 extraInfo = ::GetMessageExtraInfo(); + + return ((extraInfo & SIGNATURE_MASK) == MI_WP_SIGNATURE) && (extraInfo & 0x80); } bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window, -@@ -721,7 +723,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, +@@ -725,7 +727,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, } Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; - if (isMouseEventSynthesizedFromPenOrTouch()) { + if (isMouseEventSynthesizedFromPen()) { if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch) return false; source = Qt::MouseEventSynthesizedBySystem; -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch b/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch index 4435955d3e..342d4f044e 100644 --- a/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch +++ b/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch @@ -1,251 +1,251 @@ -From ec7b01f723961ca3efa7b279398d6680705bd453 Mon Sep 17 00:00:00 2001 +From c37ff0abfed81284721ec00665579b73107a38ca Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 15 May 2019 19:54:52 +0300 -Subject: [PATCH 19/27] Add workaround for handling table press correctly in +Subject: [PATCH 3/3] Add workaround for handling table press correctly in WinInk mode Original problem: widgets do not get synthesized mouse-down and mouse-press events until the stylus is released Reason: if the app accepts the event, WndProc should report that to the system (by returning true). This is the only way to prevent Windows from starting some system-wide gestures, like click+hold -> right button click. If we ignore the event, then OS postpones all synthesized mouse events until the entire gesture is completed. The patch implements a "hackish" workaround for the original problem by using the following rules: 1) All tablet-move events are ignored (without synthesized mouse events OS doesn't generate any Enter/Leave events) 2) All not-accepted tablet press- and release-events and also reported as ignored (without it D&D doesn't work). 3) All accepted tablet press- and release-events are reported as "accepted", **but** we artificially synthesize mouse events for them. TODO: there are still one problem: 1) Perhaps this synthesizeMouseEvent() is not needed at all. But we should first check if Qt relies on these synthesized messages anywhere in the code or not. See bug: https://bugs.kde.org/show_bug.cgi?id=406668 --- src/gui/kernel/qguiapplication.cpp | 1 + src/gui/kernel/qwindowsysteminterface.cpp | 20 ++--- src/gui/kernel/qwindowsysteminterface.h | 8 +- .../windows/qwindowspointerhandler.cpp | 88 ++++++++++++++++++- 4 files changed, 99 insertions(+), 18 deletions(-) diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp -index fd01f8bb..4d61d9fd 100644 +index a67214bd9a..d431f37d35 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp -@@ -2532,6 +2532,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T +@@ -2537,6 +2537,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T tabletEvent.setTimestamp(e->timestamp); QGuiApplication::sendSpontaneousEvent(window, &tabletEvent); pointData.state = e->buttons; + e->eventAccepted = tabletEvent.isAccepted(); if (!tabletEvent.isAccepted() && !QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse && qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)) { diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp -index b0f28691..b3b6167c 100644 +index b0f2869128..b3b6167c9d 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -949,7 +949,7 @@ void QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(boo platformSynthesizesMouse = v; } -void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) @@ -960,36 +960,36 @@ void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, QHighDpi::fromNativePixels(global, window), device, pointerType, buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); - QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); + return QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); } -void QWindowSystemInterface::handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { ulong time = QWindowSystemInterfacePrivate::eventTime.elapsed(); - handleTabletEvent(window, time, local, global, device, pointerType, buttons, pressure, + return handleTabletEvent(window, time, local, global, device, pointerType, buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); } #if QT_DEPRECATED_SINCE(5, 10) -void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { - handleTabletEvent(window, timestamp, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, - xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); + return handleTabletEvent(window, timestamp, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, + xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); } -void QWindowSystemInterface::handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { - handleTabletEvent(window, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, - xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); + return handleTabletEvent(window, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, + xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); } #endif // QT_DEPRECATED_SINCE(5, 10) diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h -index bf98c33a..fdc5a2fb 100644 +index bf98c33a1a..fdc5a2fb50 100644 --- a/src/gui/kernel/qwindowsysteminterface.h +++ b/src/gui/kernel/qwindowsysteminterface.h @@ -247,20 +247,20 @@ public: static void handleFileOpenEvent(const QString& fileName); static void handleFileOpenEvent(const QUrl &url); - static void handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, + static bool handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); - static void handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, + static bool handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); #if QT_DEPRECATED_SINCE(5, 10) - QT_DEPRECATED static void handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, + QT_DEPRECATED static bool handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); - QT_DEPRECATED static void handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, + QT_DEPRECATED static bool handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -index 5ba58c24..7319d83b 100644 +index bf201b87e0..60112f7610 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -@@ -535,6 +535,58 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, +@@ -541,6 +541,58 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, return false; // Allow mouse messages to be generated. } +void synthesizeMouseEvent(QEvent::Type type, Qt::MouseButton button, const POINTER_PEN_INFO &penInfo) +{ + // Update the cursor position + BOOL result = SetCursorPos(penInfo.pointerInfo.ptPixelLocationRaw.x, penInfo.pointerInfo.ptPixelLocationRaw.y); + if (!result) { + qCDebug(lcQpaEvents).noquote().nospace() << showbase + << __FUNCTION__ << "SetCursorPos failed, err" << GetLastError(); + return; + } + // Send mousebutton down/up events. Windows stores the button state. + DWORD inputDataFlags = 0; + switch (type) { + case QEvent::TabletPress: + switch (button) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTDOWN; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTDOWN; + break; + default: + return; + } + break; + case QEvent::TabletRelease: + switch (button) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTUP; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTUP; + break; + default: + return; + } + break; + case QEvent::TabletMove: + default: + return; + } + INPUT inputData = {}; + inputData.type = INPUT_MOUSE; + inputData.mi.dwFlags = inputDataFlags; + inputData.mi.dwExtraInfo = 0xFF515700 | 0x01; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320%28v=vs.85%29.aspx + UINT result2 = SendInput(1, &inputData, sizeof(inputData)); + if (result2 != 1) { + qCDebug(lcQpaEvents).noquote().nospace() << showbase + << __FUNCTION__ << "SendInput failed, err" << GetLastError(); + return; + } +} + bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vPenInfo) { -@@ -627,10 +679,38 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin +@@ -631,10 +683,38 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin } const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); - QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, - pressure, xTilt, yTilt, tangentialPressure, rotation, z, - sourceDevice, keyModifiers); - return false; // Allow mouse messages to be generated. + const Qt::MouseButtons oldButtons = QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state; + + const bool accepted = + QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + sourceDevice, keyModifiers); + + const Qt::MouseButtons changedButtons = + oldButtons ^ QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state; + + Qt::MouseButton pressedButton = Qt::NoButton; + + const QVector supportedButtons = + {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}; + + for (Qt::MouseButton button : supportedButtons) { + if (changedButtons & button) { + pressedButton = button; + break; + } + } + + if (accepted && pressedButton != Qt::NoButton && + (msg.message == WM_POINTERDOWN || msg.message == WM_POINTERUP)) { + + QEvent::Type type = (msg.message == WM_POINTERDOWN) ? QEvent::TabletPress : QEvent::TabletRelease; + + synthesizeMouseEvent(type, pressedButton, *penInfo); + return true; + } else { + return false; // Allow mouse messages to be generated by OS + } } } return true; -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch b/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch similarity index 92% rename from 3rdparty/ext_qt/set-has-border-in-full-screen-default.patch rename to 3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch index 4622e1402e..30c0a19ef1 100644 --- a/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch +++ b/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch @@ -1,163 +1,163 @@ -From a659513803b55b09352c3a08a1c52027e389c141 Mon Sep 17 00:00:00 2001 +From 16a8ff15243a9875fa509f10d1cd9d01df9d4c6d Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 21 Nov 2018 09:06:50 +0100 -Subject: [PATCH 20/27] Windows: Add a default setting for +Subject: [PATCH 16/17] Windows: Add a default setting for hasBorderInFullScreen The hasBorderInFullScreen only has an effect when set before the window is shown or switched to fullscreen. This is currently not possible in the QML case since the window is only accessible after all properties (including visibility) have been set. Add a function to set a default value. [ChangeLog][QtPlatformHeaders][QWindowsWindowFunctions] Add a default setting for hasBorderInFullScreen Task-number: QTBUG-47247 Task-number: QTBUG-71855 Change-Id: I3952e3f34bc4eb134cf1c5265b4489fc74112688 Reviewed-by: Andre de la Rocha Reviewed-by: Andy Shaw (cherry picked from commit 7264bf19dbc47b805bb7af5df584ce1aae081962) --- .../qwindowswindowfunctions.h | 9 +++++ .../qwindowswindowfunctions.qdoc | 33 +++++++++++++++++++ .../windows/qwindowsnativeinterface.cpp | 2 ++ .../platforms/windows/qwindowswindow.cpp | 8 ++++- .../platforms/windows/qwindowswindow.h | 2 ++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h -index e51c2fde..032dcafa 100644 +index e51c2fde67..032dcafa6e 100644 --- a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h +++ b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h @@ -81,6 +81,15 @@ public: func(window, border); } + typedef void (*SetHasBorderInFullScreenDefault)(bool border); + static const QByteArray setHasBorderInFullScreenDefaultIdentifier() { return QByteArrayLiteral("WindowsSetHasBorderInFullScreenDefault"); } + static void setHasBorderInFullScreenDefault(bool border) + { + auto func = reinterpret_cast(QGuiApplication::platformFunction(setHasBorderInFullScreenDefaultIdentifier())); + if (func) + func(border); + } + typedef void (*SetWindowActivationBehaviorType)(WindowActivationBehavior); static const QByteArray setWindowActivationBehaviorIdentifier() { return QByteArrayLiteral("WindowsSetWindowActivationBehavior"); } diff --git a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc -index a52bbe06..0c52cde7 100644 +index a52bbe061b..0c52cde753 100644 --- a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc +++ b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc @@ -93,7 +93,40 @@ is true then it will enable the WS_BORDER flag in full screen mode to enable other top level windows inside the application to appear on top when required. + \note The setting must be applied before showing the window or switching it + to full screen. For QML, setHasBorderInFullScreenDefault() can be used to + set a default value. + + See also \l [QtDoc] {Fullscreen OpenGL Based Windows} +*/ + +/*! + \typedef QWindowsWindowFunctions::SetHasBorderInFullScreenDefault + \since 5.13 + + This is the typedef for the function returned by QGuiApplication::platformFunction + when passed setHasBorderInFullScreenDefaultIdentifier. +*/ + +/*! + \fn QByteArray QWindowsWindowFunctions::setHasBorderInFullScreenDefaultIdentifier() + \since 5.13 + + This function returns the bytearray that can be used to query + QGuiApplication::platformFunction to retrieve the SetHasBorderInFullScreen function. +*/ + +/*! + \fn void QWindowsWindowFunctions::setHasBorderInFullScreenDefault(bool border) + \since 5.13 + + This is a convenience function that can be used directly instead of resolving + the function pointer. \a border will be relayed to the function retrieved by + QGuiApplication. When \a border is true, the WS_BORDER flag will be set + in full screen mode for all windows by default. + See also \l [QtDoc] {Fullscreen OpenGL Based Windows} + \sa setHasBorderInFullScreen() */ /*! diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp -index 1c5be441..42193baf 100644 +index 1c5be44150..42193baf46 100644 --- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp +++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp @@ -293,6 +293,8 @@ QFunctionPointer QWindowsNativeInterface::platformFunction(const QByteArray &fun return QFunctionPointer(QWindowsWindow::setTouchWindowTouchTypeStatic); if (function == QWindowsWindowFunctions::setHasBorderInFullScreenIdentifier()) return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenStatic); + if (function == QWindowsWindowFunctions::setHasBorderInFullScreenDefaultIdentifier()) + return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenDefault); if (function == QWindowsWindowFunctions::setWindowActivationBehaviorIdentifier()) return QFunctionPointer(QWindowsNativeInterface::setWindowActivationBehavior); if (function == QWindowsWindowFunctions::isTabletModeIdentifier()) diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp -index 76a443a8..967d479a 100644 +index 7597fcb64d..6949b9c669 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp -@@ -1183,6 +1183,7 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, +@@ -1256,6 +1256,7 @@ void QWindowCreationContext::applyToMinMaxInfo(MINMAXINFO *mmi) const const char *QWindowsWindow::embeddedNativeParentHandleProperty = "_q_embedded_native_parent_handle"; const char *QWindowsWindow::hasBorderInFullScreenProperty = "_q_has_border_in_fullscreen"; +bool QWindowsWindow::m_borderInFullScreenDefault = false; QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) : QWindowsBaseWindow(aWindow), -@@ -1220,7 +1221,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) +@@ -1293,7 +1294,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) if (aWindow->isTopLevel()) setWindowIcon(aWindow->icon()); - if (aWindow->property(hasBorderInFullScreenProperty).toBool()) + if (m_borderInFullScreenDefault || aWindow->property(hasBorderInFullScreenProperty).toBool()) setFlag(HasBorderInFullScreen); clearFlag(WithinCreate); } -@@ -2823,6 +2824,11 @@ void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border +@@ -2956,6 +2957,11 @@ void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border window->setProperty(hasBorderInFullScreenProperty, QVariant(border)); } +void QWindowsWindow::setHasBorderInFullScreenDefault(bool border) +{ + m_borderInFullScreenDefault = border; +} + void QWindowsWindow::setHasBorderInFullScreen(bool border) { if (testFlag(HasBorderInFullScreen) == border) diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h -index 2675990c..0d8096dd 100644 +index ce67e46df3..958564aa86 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h -@@ -342,6 +342,7 @@ public: +@@ -340,6 +340,7 @@ public: static void setTouchWindowTouchTypeStatic(QWindow *window, QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes); void registerTouchWindow(QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes = QWindowsWindowFunctions::NormalTouch); static void setHasBorderInFullScreenStatic(QWindow *window, bool border); + static void setHasBorderInFullScreenDefault(bool border); void setHasBorderInFullScreen(bool border); static QString formatWindowTitle(const QString &title); -@@ -387,6 +388,7 @@ private: +@@ -385,6 +386,7 @@ private: // note: intentionally not using void * in order to avoid breaking x86 VkSurfaceKHR m_vkSurface = 0; #endif + static bool m_borderInFullScreenDefault; }; #ifndef QT_NO_DEBUG_STREAM -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/remove-fullscreen-border-hack.patch b/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch similarity index 85% rename from 3rdparty/ext_qt/remove-fullscreen-border-hack.patch rename to 3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch index 12adb840d5..86c5b667e9 100644 --- a/3rdparty/ext_qt/remove-fullscreen-border-hack.patch +++ b/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch @@ -1,51 +1,51 @@ -From b609d09933ac9e8dfa910d9974f62947360bba33 Mon Sep 17 00:00:00 2001 +From f14fa33b2c5452be7df163973c26a0d1222696a2 Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Thu, 18 Apr 2019 18:59:58 +0800 -Subject: [PATCH 21/27] Hack to hide 1px border with OpenGL fullscreen hack +Subject: [PATCH 17/17] Hack to hide 1px border with OpenGL fullscreen hack Unfortunately can't hide all four sides because the bug returns. Now we leave the bottom border visible, which is probably the most we can do. Ref: https://bugreports.qt.io/browse/QTBUG-41309 --- src/plugins/platforms/windows/qwindowswindow.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp -index 967d479a..c888847e 100644 +index 6949b9c669..10de52d4d6 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp -@@ -1561,7 +1561,7 @@ void QWindowsWindow::show_sys() const +@@ -1634,7 +1634,7 @@ void QWindowsWindow::show_sys() const restoreMaximize = true; } else { updateTransientParent(); - if (state & Qt::WindowMaximized) { + if (state & Qt::WindowMaximized && !(state & Qt::WindowFullScreen)) { sm = SW_SHOWMAXIMIZED; // Windows will not behave correctly when we try to maximize a window which does not // have minimize nor maximize buttons in the window frame. Windows would then ignore -@@ -1998,7 +1998,7 @@ bool QWindowsWindow::isFullScreen_sys() const +@@ -2124,7 +2124,7 @@ bool QWindowsWindow::isFullScreen_sys() const return false; QRect geometry = geometry_sys(); if (testFlag(HasBorderInFullScreen)) - geometry += QMargins(1, 1, 1, 1); + geometry += QMargins(0, 0, 0, 1); QPlatformScreen *screen = screenForGeometry(geometry); return screen && geometry == screen->geometry(); } -@@ -2069,7 +2069,11 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState) +@@ -2195,7 +2195,11 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState) const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE; const bool wasSync = testFlag(SynchronousGeometryChangeEvent); setFlag(SynchronousGeometryChangeEvent); - SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); + if (testFlag(HasBorderInFullScreen)) { + SetWindowPos(m_data.hwnd, HWND_TOP, r.left() - 1, r.top() - 1, r.width() + 2, r.height() + 1, swpf); + } else { + SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); + } if (!wasSync) clearFlag(SynchronousGeometryChangeEvent); clearFlag(MaximizeToFullScreen); -- -2.22.0.windows.1 +2.20.1.windows.1 diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index c9e125e6ee..2d975c183b 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,263 +1,273 @@ 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 -no-sql-sqlite -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -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 -openssl-linked -I ${EXTPREFIX_qt}/include -L ${EXTPREFIX_qt}/lib # -opensource -confirm-license # -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) + # MIME-type optimization patches + set(ext_qt_PATCH_COMMAND + ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Use-fast-path-for-unsupported-mime-types.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch + ) + + # Tablet support patches if (NOT USE_QT_TABLET_WINDOWS) - set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch + set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} + 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}/disable-winink.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 ) else() - set(ext_qt_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 + set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch - COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0023-Implement-a-switch-for-tablet-API-on-Windows.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0025-Disable-tablet-relative-mode-in-Qt.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0050-Fix-using-tablet-on-QML-widgets.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0051-Add-workaround-for-handling-table-press-correctly-in.patch ) endif() + + # HDR patches + set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Implement-openGL-surface-color-space-selection-in-An.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Implement-color-space-selection-for-QSurfaceFormat.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-Implement-color-conversion-for-the-backing-store-tex.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch + ) + + # Other patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} - COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/set-has-border-in-full-screen-default.patch - COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/remove-fullscreen-border-hack.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch COMMAND ${PATCH_COMMAND} -p1 -d qttools -i ${CMAKE_CURRENT_SOURCE_DIR}/windeployqt-force-allow-debug-info.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0031-Compute-logical-DPI-on-a-per-screen-basis.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0032-Update-Dpi-and-scale-factor-computation.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0034-Update-QT_SCREEN_SCALE_FACTORS.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch ) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL https://download.qt.io/archive/qt/5.12/5.12.3/single/qt-everywhere-src-5.12.3.tar.xz - URL_MD5 38017e0ed88b9baba063bd723d9ca8b2 + URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz + URL_MD5 dda95b0239d13c5276834177af3a8588 PATCH_COMMAND ${ext_qt_PATCH_COMMAND} 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 ext_openssl ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL https://download.qt.io/archive/qt/5.12/5.12.3/single/qt-everywhere-src-5.12.3.tar.xz - URL_MD5 38017e0ed88b9baba063bd723d9ca8b2 + URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz + URL_MD5 dda95b0239d13c5276834177af3a8588 - 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-for-accepted-QTabletEven.patch + PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.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 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 -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-for-accepted-QTabletEven.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 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.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.3/single/qt-everywhere-src-5.12.3.tar.xz - URL_MD5 38017e0ed88b9baba063bd723d9ca8b2 + URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz + URL_MD5 dda95b0239d13c5276834177af3a8588 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 qtwebengine -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/3rdparty/ext_qt/qtbase-configure.patch b/3rdparty/ext_qt/qtbase-configure.patch deleted file mode 100644 index ee2a621bcc..0000000000 --- a/3rdparty/ext_qt/qtbase-configure.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- old/configure 2016-06-10 02:48:56.000000000 -0400 -+++ new/configure 2016-09-21 02:26:34.000000000 -0400 -@@ -548,7 +548,7 @@ - exit 2 - fi - -- if ! /usr/bin/xcrun -find xcrun >/dev/null 2>&1; then -+ if ! /usr/bin/xcrun -find xcodebuild >/dev/null 2>&1; then - echo >&2 - echo " Xcode not set up properly. You may need to confirm the license" >&2 - echo " agreement by running /usr/bin/xcodebuild without arguments." >&2 diff --git a/krita/data/aboutdata/credits.txt b/krita/data/aboutdata/credits.txt index 5b9efe7042..82ec941b42 100644 --- a/krita/data/aboutdata/credits.txt +++ b/krita/data/aboutdata/credits.txt @@ -1,8 +1,10 @@ Peter Sikking:Project Vision Ramon Miranda:Artist, Muses author, brush and preset creator David Revoy:Brushes and Palettes Ilya Portnov:MyPaint shade selector Martin Renold:MyPaint shade selector Saisho Kazuki:Japanese Animation template Pasquale D'Antuono:Donor +David Gowers:Dither patterns + diff --git a/krita/data/patterns/CMakeLists.txt b/krita/data/patterns/CMakeLists.txt index 09fb559736..6f5917648f 100644 --- a/krita/data/patterns/CMakeLists.txt +++ b/krita/data/patterns/CMakeLists.txt @@ -1,86 +1,117 @@ ########### install files ############### install(FILES Abstract_lines.png 01_canvas.png 02_rough-canvas.png 02b_WoofTissue.png 03_default-paper.png 04_paper-C-grain.png 05_paper-torchon.png 06_hard-grain.png 08_bump-relief.png 09_drawed_crosshatched.png 09b_drawed-CrossedLines.png 10_drawed_dotted.png 11_drawed_furry.png 12_drawed_vertical.png 13_drawed_swirl.png 14_texture-rock.png 15_texture-rockB.png 16_texture-woody.png 17_texture-melt.png 18_texture-bark.png 18b_WaveFlex.png 19_texture-crackle.png 20_texture-vegetal.png 21_texture-chainmail.png 22_texture-reptile.png 23-dynamic-screentone-A.png 24-dynamic-screentone-B.png 25-dynamic-screentone-C.png 26_brush-marks.png Cross01.pat Cross02.pat Cross03.pat Cross04.pat Cross05.pat Cross06.pat Cross07.pat Crumpled_Paper.pat Grid01.pat Grid02.pat Grid03.pat Grid04.pat Grid05.pat HR_PastelPaper_02.pat HR_Wall_Paper.pat Maze_lines.png Pattern01.pat Pattern02.pat Pattern03.pat Pattern04.pat Pattern05.pat Pattern06.pat Rough_Paper.png Rough_Wall_With_Impasto.png Sand_fine.png Stars_Sized.png Squares01.pat Squares02.pat Squares03.pat Squares04.pat Squares05.pat Squares06.pat Squares07.pat Squares08.pat Squares09.pat Squares10.pat Stripes02.pat Stripes03.pat Stripes04.pat Stripes05.pat Stripes06.pat Stripes07.pat Stripes08.pat Stripes09.pat Zigzag01.pat Zigzag02.pat Zigzag03.pat Zigzag04.pat fractal_pattern.pat generic_paper1.pat generic_paper2.pat hexacolBW__2.pat +DITH_0202_CLUS.pat +DITH_0202_GEN_.pat +DITH_0202_HORZ.pat +DITH_0202_VERT.pat +DITH_0404_ALT_.pat +DITH_0404_BL22.pat +DITH_0404_CLUS.pat +DITH_0404_CURL.pat +DITH_0404_DIAG.pat +DITH_0404_ELL2.pat +DITH_0404_ELL3.pat +DITH_0404_ELLS.pat +DITH_0404_GEN_.pat +DITH_0404_HORZ.pat +DITH_0404_SHUR.pat +DITH_0404_SLIC.pat +DITH_0404_VERT.pat +DITH_0404_WAV2.pat +DITH_0404_WAVE.pat +DITH_0404_ZORO.pat +DITH_0808_BL22.pat +DITH_0808_BL22_v.pat +DITH_0808_BUBL.pat +DITH_0808_CIRC.pat +DITH_0808_CLUS.pat +DITH_0808_DIAM.pat +DITH_0808_PANL.pat +DITH_0808_SPOT.pat +DITH_0808_SWRL.pat +DITH_0808_WAVE.pat +DITH_3232_CSTR.pat DESTINATION ${DATA_INSTALL_DIR}/krita/patterns) diff --git a/krita/data/patterns/DITH_0202_CLUS.pat b/krita/data/patterns/DITH_0202_CLUS.pat new file mode 100644 index 0000000000..a490dd26ed Binary files /dev/null and b/krita/data/patterns/DITH_0202_CLUS.pat differ diff --git a/krita/data/patterns/DITH_0202_GEN_.pat b/krita/data/patterns/DITH_0202_GEN_.pat new file mode 100644 index 0000000000..1957ce2b67 Binary files /dev/null and b/krita/data/patterns/DITH_0202_GEN_.pat differ diff --git a/krita/data/patterns/DITH_0202_HORZ.pat b/krita/data/patterns/DITH_0202_HORZ.pat new file mode 100644 index 0000000000..2168049001 Binary files /dev/null and b/krita/data/patterns/DITH_0202_HORZ.pat differ diff --git a/krita/data/patterns/DITH_0202_VERT.pat b/krita/data/patterns/DITH_0202_VERT.pat new file mode 100644 index 0000000000..7fcf9acac4 Binary files /dev/null and b/krita/data/patterns/DITH_0202_VERT.pat differ diff --git a/krita/data/patterns/DITH_0404_ALT_.pat b/krita/data/patterns/DITH_0404_ALT_.pat new file mode 100644 index 0000000000..03acba8794 Binary files /dev/null and b/krita/data/patterns/DITH_0404_ALT_.pat differ diff --git a/krita/data/patterns/DITH_0404_BL22.pat b/krita/data/patterns/DITH_0404_BL22.pat new file mode 100644 index 0000000000..8862a86b53 Binary files /dev/null and b/krita/data/patterns/DITH_0404_BL22.pat differ diff --git a/krita/data/patterns/DITH_0404_CLUS.pat b/krita/data/patterns/DITH_0404_CLUS.pat new file mode 100644 index 0000000000..2ee2d9e7a4 Binary files /dev/null and b/krita/data/patterns/DITH_0404_CLUS.pat differ diff --git a/krita/data/patterns/DITH_0404_CURL.pat b/krita/data/patterns/DITH_0404_CURL.pat new file mode 100644 index 0000000000..0ba3bd0570 Binary files /dev/null and b/krita/data/patterns/DITH_0404_CURL.pat differ diff --git a/krita/data/patterns/DITH_0404_DIAG.pat b/krita/data/patterns/DITH_0404_DIAG.pat new file mode 100644 index 0000000000..23d6efa933 Binary files /dev/null and b/krita/data/patterns/DITH_0404_DIAG.pat differ diff --git a/krita/data/patterns/DITH_0404_ELL2.pat b/krita/data/patterns/DITH_0404_ELL2.pat new file mode 100644 index 0000000000..72adb0a39e Binary files /dev/null and b/krita/data/patterns/DITH_0404_ELL2.pat differ diff --git a/krita/data/patterns/DITH_0404_ELL3.pat b/krita/data/patterns/DITH_0404_ELL3.pat new file mode 100644 index 0000000000..502ed21949 Binary files /dev/null and b/krita/data/patterns/DITH_0404_ELL3.pat differ diff --git a/krita/data/patterns/DITH_0404_ELLS.pat b/krita/data/patterns/DITH_0404_ELLS.pat new file mode 100644 index 0000000000..0fa32df133 Binary files /dev/null and b/krita/data/patterns/DITH_0404_ELLS.pat differ diff --git a/krita/data/patterns/DITH_0404_GEN_.pat b/krita/data/patterns/DITH_0404_GEN_.pat new file mode 100644 index 0000000000..7dfe88bbdc Binary files /dev/null and b/krita/data/patterns/DITH_0404_GEN_.pat differ diff --git a/krita/data/patterns/DITH_0404_HORZ.pat b/krita/data/patterns/DITH_0404_HORZ.pat new file mode 100644 index 0000000000..de52f84159 Binary files /dev/null and b/krita/data/patterns/DITH_0404_HORZ.pat differ diff --git a/krita/data/patterns/DITH_0404_SHUR.pat b/krita/data/patterns/DITH_0404_SHUR.pat new file mode 100644 index 0000000000..6f355da51c Binary files /dev/null and b/krita/data/patterns/DITH_0404_SHUR.pat differ diff --git a/krita/data/patterns/DITH_0404_SLIC.pat b/krita/data/patterns/DITH_0404_SLIC.pat new file mode 100644 index 0000000000..73f5c74cd1 Binary files /dev/null and b/krita/data/patterns/DITH_0404_SLIC.pat differ diff --git a/krita/data/patterns/DITH_0404_VERT.pat b/krita/data/patterns/DITH_0404_VERT.pat new file mode 100644 index 0000000000..d086e36830 Binary files /dev/null and b/krita/data/patterns/DITH_0404_VERT.pat differ diff --git a/krita/data/patterns/DITH_0404_WAV2.pat b/krita/data/patterns/DITH_0404_WAV2.pat new file mode 100644 index 0000000000..a01d844e7b Binary files /dev/null and b/krita/data/patterns/DITH_0404_WAV2.pat differ diff --git a/krita/data/patterns/DITH_0404_WAVE.pat b/krita/data/patterns/DITH_0404_WAVE.pat new file mode 100644 index 0000000000..cd9b5721a3 Binary files /dev/null and b/krita/data/patterns/DITH_0404_WAVE.pat differ diff --git a/krita/data/patterns/DITH_0404_ZORO.pat b/krita/data/patterns/DITH_0404_ZORO.pat new file mode 100644 index 0000000000..919002432d Binary files /dev/null and b/krita/data/patterns/DITH_0404_ZORO.pat differ diff --git a/krita/data/patterns/DITH_0808_BL22.pat b/krita/data/patterns/DITH_0808_BL22.pat new file mode 100644 index 0000000000..2683b8a2f5 Binary files /dev/null and b/krita/data/patterns/DITH_0808_BL22.pat differ diff --git a/krita/data/patterns/DITH_0808_BL22_v.pat b/krita/data/patterns/DITH_0808_BL22_v.pat new file mode 100644 index 0000000000..ed86f75bf8 Binary files /dev/null and b/krita/data/patterns/DITH_0808_BL22_v.pat differ diff --git a/krita/data/patterns/DITH_0808_BUBL.pat b/krita/data/patterns/DITH_0808_BUBL.pat new file mode 100644 index 0000000000..cfe3d45ac9 Binary files /dev/null and b/krita/data/patterns/DITH_0808_BUBL.pat differ diff --git a/krita/data/patterns/DITH_0808_CIRC.pat b/krita/data/patterns/DITH_0808_CIRC.pat new file mode 100644 index 0000000000..b4d40d4650 Binary files /dev/null and b/krita/data/patterns/DITH_0808_CIRC.pat differ diff --git a/krita/data/patterns/DITH_0808_CLUS.pat b/krita/data/patterns/DITH_0808_CLUS.pat new file mode 100644 index 0000000000..a0c41af628 Binary files /dev/null and b/krita/data/patterns/DITH_0808_CLUS.pat differ diff --git a/krita/data/patterns/DITH_0808_DIAM.pat b/krita/data/patterns/DITH_0808_DIAM.pat new file mode 100644 index 0000000000..3e72373753 Binary files /dev/null and b/krita/data/patterns/DITH_0808_DIAM.pat differ diff --git a/krita/data/patterns/DITH_0808_PANL.pat b/krita/data/patterns/DITH_0808_PANL.pat new file mode 100644 index 0000000000..e84dfddede Binary files /dev/null and b/krita/data/patterns/DITH_0808_PANL.pat differ diff --git a/krita/data/patterns/DITH_0808_SPOT.pat b/krita/data/patterns/DITH_0808_SPOT.pat new file mode 100644 index 0000000000..7eddc95cae Binary files /dev/null and b/krita/data/patterns/DITH_0808_SPOT.pat differ diff --git a/krita/data/patterns/DITH_0808_SWRL.pat b/krita/data/patterns/DITH_0808_SWRL.pat new file mode 100644 index 0000000000..3a9abbdc12 Binary files /dev/null and b/krita/data/patterns/DITH_0808_SWRL.pat differ diff --git a/krita/data/patterns/DITH_0808_WAVE.pat b/krita/data/patterns/DITH_0808_WAVE.pat new file mode 100644 index 0000000000..f889ade6af Binary files /dev/null and b/krita/data/patterns/DITH_0808_WAVE.pat differ diff --git a/krita/data/patterns/DITH_3232_CSTR.pat b/krita/data/patterns/DITH_3232_CSTR.pat new file mode 100644 index 0000000000..595932a911 Binary files /dev/null and b/krita/data/patterns/DITH_3232_CSTR.pat differ diff --git a/krita/data/patterns/dith_license.txt b/krita/data/patterns/dith_license.txt new file mode 100644 index 0000000000..05a4d72068 --- /dev/null +++ b/krita/data/patterns/dith_license.txt @@ -0,0 +1,465 @@ +"dithering/halftoning patterns" by David Gowers + +Files: + +DITH_0202_CLUS.pat +DITH_0202_GEN_.pat +DITH_0202_HORZ.pat +DITH_0202_VERT.pat +DITH_0404_ALT_.pat +DITH_0404_BL22.pat +DITH_0404_CLUS.pat +DITH_0404_CURL.pat +DITH_0404_DIAG.pat +DITH_0404_ELL2.pat +DITH_0404_ELL3.pat +DITH_0404_ELLS.pat +DITH_0404_GEN_.pat +DITH_0404_HORZ.pat +DITH_0404_SHUR.pat +DITH_0404_SLIC.pat +DITH_0404_VERT.pat +DITH_0404_WAV2.pat +DITH_0404_WAVE.pat +DITH_0404_ZORO.pat +DITH_0808_BL22.pat +DITH_0808_BL22_v.pat +DITH_0808_BUBL.pat +DITH_0808_CIRC.pat +DITH_0808_CLUS.pat +DITH_0808_DIAM.pat +DITH_0808_PANL.pat +DITH_0808_SPOT.pat +DITH_0808_SWRL.pat +DITH_0808_WAVE.pat +DITH_3232_CSTR.pat + +Licensed under CC-BY-SA: + +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 0fd03736b9..296f70217a 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,299 +1,323 @@ 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 Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Krita Vakfı Фундація 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 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.

+

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

+

El 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는 디지털 예술 스튜디오입니다.

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는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.

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 avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques ú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 のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

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://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. + Krita és un estudi de pintura digital ple de funcionalitats. + Krita es un completo estudio de dibujo digital. + Krita è uno studio d'arte digitale completo. + Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. O Krita é um estúdio de arte digital completo. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. + La finestra d'inici ara proporciona les darreres noticies quant al Krita. + La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. + La finestra di avvio ora fornisce anche le ultime novità su Krita. + 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. + Hi ha uns deu motors de pinzell immensament potents. + Existen unos diez inmensamente potentes motores de pinceles. + Ci sono oltre dieci motori di pennelli incredibilmente potenti. + 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. Existem mais de dez motores de pincéis extremamente poderosos. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. + Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. + Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. + Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. + 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. + Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. + ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. + Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. + 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. + Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. + Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. + Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. + 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics KDE krita org.kde.krita.desktop
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index f25eb16d1b..94db99a8dc 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1930 +1,1933 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * 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 "SvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include #include #include "kis_dom_utils.h" #include "kis_algebra_2d.h" #include "kis_debug.h" #include "kis_global.h" #include struct SvgParser::DeferredUseStore { struct El { El(const KoXmlElement* ue, const QString& key) : m_useElement(ue), m_key(key) { } const KoXmlElement* m_useElement; QString m_key; }; DeferredUseStore(SvgParser* p) : m_parse(p) { } void add(const KoXmlElement* useE, const QString& key) { m_uses.push_back(El(useE, key)); } bool empty() const { return m_uses.empty(); } void checkPendingUse(const KoXmlElement &b, QList& shapes) { KoShape* shape = 0; const QString id = b.attribute("id"); if (id.isEmpty()) return; // debugFlake << "Checking id: " << id; auto i = std::partition(m_uses.begin(), m_uses.end(), [&](const El& e) -> bool {return e.m_key != id;}); while (i != m_uses.end()) { const El& el = m_uses.back(); if (m_parse->m_context.hasDefinition(el.m_key)) { // debugFlake << "Found pending use for id: " << el.m_key; shape = m_parse->resolveUse(*(el.m_useElement), el.m_key); if (shape) { shapes.append(shape); } } m_uses.pop_back(); } } ~DeferredUseStore() { while (!m_uses.empty()) { const El& el = m_uses.back(); debugFlake << "WARNING: could not find path in m_uses; }; SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { qDeleteAll(m_symbols); } KoXmlDocument SvgParser::createDocumentFromSvg(QIODevice *device, QString *errorMsg, int *errorLine, int *errorColumn) { QXmlInputSource source(device); return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn); } KoXmlDocument SvgParser::createDocumentFromSvg(const QByteArray &data, QString *errorMsg, int *errorLine, int *errorColumn) { QXmlInputSource source; source.setData(data); return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn); } KoXmlDocument SvgParser::createDocumentFromSvg(const QString &data, QString *errorMsg, int *errorLine, int *errorColumn) { QXmlInputSource source; source.setData(data); return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn); } KoXmlDocument SvgParser::createDocumentFromSvg(QXmlInputSource *source, QString *errorMsg, int *errorLine, int *errorColumn) { // we should read all spaces to parse text node correctly QXmlSimpleReader reader; reader.setFeature("http://qt-project.org/xml/features/report-whitespace-only-CharData", true); reader.setFeature("http://xml.org/sax/features/namespaces", false); reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); QDomDocument doc; if (!doc.setContent(source, &reader, errorMsg, errorLine, errorColumn)) { return QDomDocument(); } return doc; } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); setFileFetcher( [this](const QString &name) { const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name; QFile file(fileName); if (!file.exists()) { return QByteArray(); } file.open(QIODevice::ReadOnly); return file.readAll(); }); } void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch) { KIS_ASSERT(!m_context.currentGC()); m_context.pushGraphicsContext(); m_context.currentGC()->isResolutionFrame = true; m_context.currentGC()->pixelsPerInch = pixelsPerInch; const qreal scale = 72.0 / pixelsPerInch; const QTransform t = QTransform::fromScale(scale, scale); m_context.currentGC()->currentBoundingBox = boundsInPixels; m_context.currentGC()->matrix = t; } void SvgParser::setForcedFontSizeResolution(qreal value) { if (qFuzzyCompare(value, 0.0)) return; m_context.currentGC()->forcedFontSizeCoeff = 72.0 / value; } QList SvgParser::shapes() const { return m_shapes; } QVector SvgParser::takeSymbols() { QVector symbols = m_symbols; m_symbols.clear(); return symbols; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (KoXml::childNodesCount(e) == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { debugFlake << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape) { /** * Unlike the gradient parsing function, this method is called every time we * *reference* the pattern, not when we define it. Therefore we can already * use the coordinate system of the destination. */ QSharedPointer pattHelper; SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return pattHelper; const QString patternId = e.attribute("id"); if (patternId.isEmpty()) return pattHelper; pattHelper = toQShared(new KoVectorPatternBackground); if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty() &&href != patternId) { // copy the referenced pattern if found QSharedPointer pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * * TODO: Please also note that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer marker(new KoMarker()); marker->setCoordinateSystem( KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth"))); marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")), parseUnitY(e.attribute("refY")))); marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")), parseUnitY(e.attribute("markerHeight", "3")))); const QString orientation = e.attribute("orient", "0"); if (orientation == "auto") { marker->setAutoOrientation(true); } else { marker->setExplicitOrientation(parseAngular(orientation)); } // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize()); KoShape *markerShape = parseGroup(e); m_context.popGraphicsContext(); if (!markerShape) return false; marker->setShapes({markerShape}); m_markers.insert(id, QExplicitlySharedDataPointer(marker.take())); return true; } bool SvgParser::parseSymbol(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer svgSymbol(new KoSvgSymbol()); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0); QString title = e.firstChildElement("title").toElement().text(); QScopedPointer symbolShape(parseGroup(e)); m_context.popGraphicsContext(); if (!symbolShape) return false; svgSymbol->shape = symbolShape.take(); svgSymbol->title = title; svgSymbol->id = id; if (title.isEmpty()) svgSymbol->title = id; if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) { debugFlake << "Symbol" << id << "seems to be empty, discarding"; return false; } m_symbols << svgSymbol.take(); return true; } bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; applyCurrentBasicStyle(shape); if (KoPathShape *pathShape = dynamic_cast(shape)) { applyMarkers(pathShape); } applyFilter(shape); applyClipping(shape, shapeToOriginalUserCoordinates); applyMaskClipping(shape, shapeToOriginalUserCoordinates); } void SvgParser::applyCurrentBasicStyle(KoShape *shape) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (!gc->display || !gc->visible) { /** * WARNING: here is a small inconsistency with the standard: * in the standard, 'display' is not inherited, but in * flake it is! * * NOTE: though the standard says: "A value of 'display:none' indicates * that the given element and ***its children*** shall not be * rendered directly". Therefore, using setVisible(false) is fully * legitimate here (DK 29.11.16). */ shape->setVisible(false); } shape->setTransparency(1.0 - gc->opacity); } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates) { applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(gradient->gradient()); QLinearGradient *g = new QLinearGradient(); g->setStart(userToRelative.map(o->start())); g->setFinalStop(userToRelative.map(o->finalStop())); g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStops(o->stops()); g->setSpread(o->spread()); resultGradient = g; *transform = relativeToUser * gradient->transform() * userToRelative; } else if (gradient->gradient()->type() == QGradient::RadialGradient) { // For radial and conical gradients such conversion is not possible resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform() * gc->matrix * shape->transformation().inverted(); const QRectF outlineRect = shape->outlineRect(); if (outlineRect.isEmpty()) return resultGradient; /** * If shape outline rect is valid, convert the gradient into OBB mode by * doing some magic conversions: we compensate non-uniform size of the shape * by applying an additional pre-transform */ QRadialGradient *rgradient = static_cast(resultGradient); const qreal maxDimension = KisAlgebra2D::maxDimension(outlineRect); const QRectF uniformSize(outlineRect.topLeft(), QSizeF(maxDimension, maxDimension)); const QTransform uniformizeTransform = QTransform::fromTranslate(-outlineRect.x(), -outlineRect.y()) * QTransform::fromScale(maxDimension / shape->outlineRect().width(), maxDimension / shape->outlineRect().height()) * QTransform::fromTranslate(outlineRect.x(), outlineRect.y()); const QPointF centerLocal = transform->map(rgradient->center()); const QPointF focalLocal = transform->map(rgradient->focalPoint()); const QPointF centerOBB = KisAlgebra2D::absoluteToRelative(centerLocal, uniformSize); const QPointF focalOBB = KisAlgebra2D::absoluteToRelative(focalLocal, uniformSize); rgradient->setCenter(centerOBB); rgradient->setFocalPoint(focalOBB); const qreal centerRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->centerRadius(), uniformSize); const qreal focalRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->focalRadius(), uniformSize); rgradient->setCenterRadius(centerRadiusOBB); rgradient->setFocalRadius(focalRadiusOBB); rgradient->setCoordinateMode(QGradient::ObjectBoundingMode); // Warning: should it really be pre-multiplication? *transform = uniformizeTransform * gradient->transform(); } } return resultGradient; } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector dashes = srcStroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) { dashes[i] /= lineWidth; } dstStroke->setLineStyle(Qt::CustomDashLine, dashes); dstStroke->setDashOffset(dashOffset / lineWidth); } else { dstStroke->setLineStyle(Qt::SolidLine, QVector()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox transformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e, DeferredUseStore* deferredUseStore) { QString href = e.attribute("xlink:href"); if (href.isEmpty()) return 0; QString key = href.mid(1); const bool gotDef = m_context.hasDefinition(key); if (gotDef) { return resolveUse(e, key); } else if (deferredUseStore) { deferredUseStore->add(&e, key); return 0; } debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: " << key; return 0; } KoShape* SvgParser::resolveUse(const KoXmlElement &e, const QString& key) { KoShape *result = 0; SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: parse 'width' and 'height' as well gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); const KoXmlElement &referencedElement = m_context.definition(key); result = parseGroup(e, referencedElement); m_context.popGraphicsContext(); return result; } void SvgParser::addToGroup(QList shapes, KoShapeContainer *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not normalized KoShapeGroupCommand cmd(group, shapes, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); qreal width = w.isEmpty() ? 666.0 : parseUnitX(w); qreal height = h.isEmpty() ? 555.0 : parseUnitY(h); if (w.isEmpty() || h.isEmpty()) { QRectF viewRect; QTransform viewTransform_unused; QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0); if (SvgUtil::parseViewBox(e, fakeBoundingRect, &viewRect, &viewTransform_unused)) { QSizeF estimatedSize = viewRect.size(); if (estimatedSize.isValid()) { if (!w.isEmpty()) { estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width()); } else if (!h.isEmpty()) { estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height); } width = estimatedSize.width(); height = estimatedSize.height(); } } } QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; // First find the metadata for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (b.tagName() == "title") { m_documentTitle = b.text().trimmed(); } else if (b.tagName() == "desc") { m_documentDescription = b.text().trimmed(); } else if (b.tagName() == "metadata") { // TODO: parse the metadata } } // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy // and as mother makes them -- if mother is inkscape. if (gc->currentBoundingBox.normalized().isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } QString SvgParser::documentTitle() const { return m_documentTitle; } QString SvgParser::documentDescription() const { return m_documentDescription; } void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * coordinateSystemOnLoading; KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate); return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy()); } KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom) { m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); // groups should also have their own coordinate system! group->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix); uploadStyleToContext(b); QList childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom, 0); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } KoShape* SvgParser::parseTextNode(const KoXmlText &e) { QScopedPointer textChunk(new KoSvgTextChunkShape()); textChunk->setZIndex(m_context.nextZIndex()); if (!textChunk->loadSvgTextNode(e, m_context)) { return 0; } textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix); applyCurrentBasicStyle(textChunk.data()); // apply style to this group after size is set return textChunk.take(); } KoXmlText getTheOnlyTextChild(const KoXmlElement &e) { KoXmlNode firstChild = e.firstChild(); return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ? firstChild.toText() : KoXmlText(); } KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *mergeIntoShape) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan", 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_isInsideTextSubtree || e.tagName() == "text", 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0); KoSvgTextShape *rootTextShape = 0; if (e.tagName() == "text") { // XXX: Shapes need to be created by their factories if (mergeIntoShape) { rootTextShape = mergeIntoShape; } else { rootTextShape = new KoSvgTextShape(); const QString useRichText = e.attribute("krita:useRichText", "true"); rootTextShape->setRichTextPreferred(useRichText != "false"); } } if (rootTextShape) { m_isInsideTextSubtree = true; } m_context.pushGraphicsContext(e); uploadStyleToContext(e); KoSvgTextChunkShape *textChunk = rootTextShape ? rootTextShape : new KoSvgTextChunkShape(); - textChunk->setZIndex(m_context.nextZIndex()); + + if (!mergeIntoShape) { + textChunk->setZIndex(m_context.nextZIndex()); + } textChunk->loadSvg(e, m_context); // 1) apply transformation only in case we are not overriding the shape! // 2) the transformation should be applied *before* the shape is added to the group! if (!mergeIntoShape) { // groups should also have their own coordinate system! textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(textChunk, m_context.currentGC()->matrix); // handle id applyId(e.attribute("id"), textChunk); applyCurrentStyle(textChunk, extraOffset); // apply style to this group after size is set } else { m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation(0); applyCurrentBasicStyle(textChunk); } KoXmlText onlyTextChild = getTheOnlyTextChild(e); if (!onlyTextChild.isNull()) { textChunk->loadSvgTextNode(onlyTextChild, m_context); } else { QList childShapes = parseContainer(e, true); addToGroup(childShapes, textChunk); } m_context.popGraphicsContext(); textChunk->normalizeCharTransformations(); if (rootTextShape) { textChunk->simplifyFillStrokeInheritance(); m_isInsideTextSubtree = false; rootTextShape->relayout(); } return textChunk; } QList SvgParser::parseContainer(const KoXmlElement &e, bool parseTextNodes) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; DeferredUseStore deferredUseStore(this); for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) { if (parseTextNodes && n.isText()) { KoShape *shape = parseTextNode(n.toText()); if (shape) { shapes += shape; } } continue; } if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemented yet } } QList currentShapes = parseSingleElement(b, &deferredUseStore); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } void SvgParser::parseDefsElement(const KoXmlElement &e) { KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs"); parseSingleElement(e); } QList SvgParser::parseSingleElement(const KoXmlElement &b, DeferredUseStore* deferredUseStore) { QList shapes; // save definition for later instantiation with 'use' m_context.addDefinition(b); if (deferredUseStore) { deferredUseStore->checkPendingUse(b, shapes); } if (b.tagName() == "svg") { shapes += parseSvg(b); } else if (b.tagName() == "g" || b.tagName() == "a") { // treat svg link as group so we don't miss its child elements shapes += parseGroup(b); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { if (KoXml::childNodesCount(b) > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); m_defsShapes << defsShape; // TODO: where to delete the shape!? } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); } else if (b.tagName() == "symbol") { parseSymbol(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "text" || b.tagName() == "tspan") { shapes += parseTextElement(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image") { KoShape *shape = createObjectDirect(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { KoShape* s = parseUse(b, deferredUseStore); if (s) { shapes += s; } } else if (b.tagName() == "color-profile") { m_context.parseProfile(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } } return shapes; } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QStringList pointList = SvgUtil::simplifyList(element.attribute("points")); for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it))); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it))); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } KoShape *SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) { shape->setShapeId(factory->id()); } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border // ??? KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/global/kis_signal_auto_connection.h b/libs/global/kis_signal_auto_connection.h index 89205ab67c..f618af22a2 100644 --- a/libs/global/kis_signal_auto_connection.h +++ b/libs/global/kis_signal_auto_connection.h @@ -1,137 +1,131 @@ /* * Copyright (c) 2015 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_SIGNAL_AUTO_CONNECTOR_H #define __KIS_SIGNAL_AUTO_CONNECTOR_H #include #include #include - /** * A special wrapper class that represents a connection between two QObject objects. * It creates the connection on the construction and disconnects it on destruction. * * WARNING: never use QScopedPointer::reset() for updating the * connection like: * * QScopedPointer conn; * ... * void Something::setCanvas(KoCanvasBase * canvas) { * conn.reset(new KisSignalAutoConnection(...)); * } * * The object stored in a scoped pointer will be destructed *after* * the new object created which will cause you object to become * disconnected. * * Instead use two-stage updates: * conn.reset(); * conn.reset(new KisSignalAutoConnection(...)); */ class KisSignalAutoConnection { public: /** * Creates a connection object and starts the requested connection */ - inline KisSignalAutoConnection(const QObject *sender, const char *signal, - const QObject *receiver, const char *method, + template + inline KisSignalAutoConnection(Sender sender, Signal signal, + Receiver receiver, Method method, Qt::ConnectionType type = Qt::AutoConnection) - : m_sender(const_cast(sender)), - m_signal(signal), - m_receiver(const_cast(receiver)), - m_method(method) + : m_connection(QObject::connect(sender, signal, receiver, method, type)) { - QObject::connect(m_sender, m_signal, m_receiver, m_method, type); } + inline ~KisSignalAutoConnection() { - if (!m_sender.isNull() && !m_receiver.isNull()) { - QObject::disconnect(m_sender, m_signal, m_receiver, m_method); - } + QObject::disconnect(m_connection); } private: KisSignalAutoConnection(const KisSignalAutoConnection &rhs); private: - QPointer m_sender; - const char *m_signal; - QPointer m_receiver; - const char *m_method; + QMetaObject::Connection m_connection; }; typedef QSharedPointer KisSignalAutoConnectionSP; /** * A class to store multiple connections and to be able to stop all of * them at once. It is handy when you need to reconnect some other * object to the current manager. Then you just call * connectionsStore.clear() and then call addConnection() again to * recreate them. */ class KisSignalAutoConnectionsStore { public: /** * Connects \p sender to \p receiver with a connection of type \p type. * The connection is saved into the store so can be reset later with clear() * * \see addUniqueConnection() */ - inline void addConnection(const QObject *sender, const char *signal, - const QObject *receiver, const char *method, + template + inline void addConnection(Sender sender, Signal signal, + Receiver receiver, Method method, Qt::ConnectionType type = Qt::AutoConnection) { m_connections.append(KisSignalAutoConnectionSP( new KisSignalAutoConnection(sender, signal, receiver, method, type))); } /** * Convenience override for addConnection() that creates a unique connection * * \see addConnection() */ - inline void addUniqueConnection(const QObject *sender, const char *signal, - const QObject *receiver, const char *method) + template + inline void addUniqueConnection(Sender sender, Signal signal, + Receiver receiver, Method method) { m_connections.append(KisSignalAutoConnectionSP( new KisSignalAutoConnection(sender, signal, receiver, method, Qt::UniqueConnection))); } /** * Disconnects all the stored connections and removes them from the store */ inline void clear() { m_connections.clear(); } inline bool isEmpty() { return m_connections.isEmpty(); } private: QVector m_connections; }; #endif /* __KIS_SIGNAL_AUTO_CONNECTOR_H */ diff --git a/libs/global/tests/CMakeLists.txt b/libs/global/tests/CMakeLists.txt index 22d062e090..ca8e3eff8f 100644 --- a/libs/global/tests/CMakeLists.txt +++ b/libs/global/tests/CMakeLists.txt @@ -1,8 +1,9 @@ include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests(KisSharedThreadPoolAdapterTest.cpp + KisSignalAutoConnectionTest.cpp NAME_PREFIX libs-global- LINK_LIBRARIES kritaglobal Qt5::Test) diff --git a/libs/global/tests/KisSignalAutoConnectionTest.cpp b/libs/global/tests/KisSignalAutoConnectionTest.cpp new file mode 100644 index 0000000000..45fcf2ed5f --- /dev/null +++ b/libs/global/tests/KisSignalAutoConnectionTest.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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 "KisSignalAutoConnectionTest.h" + +#include + +void KisSignalAutoConnectionTest::testMacroConnection() +{ + QScopedPointer test1(new TestClass()); + QScopedPointer test2(new TestClass()); + KisSignalAutoConnectionsStore conn; + conn.addConnection(test1.data(), SIGNAL(sigTest1()), test2.data(), SLOT(slotTest1())); + emit test1->sigTest1(); + QVERIFY(test2->m_test1Called); + test2->m_test1Called = false; + conn.clear(); + emit test1->sigTest1(); + QVERIFY(test2->m_test1Called == false); +} + +void KisSignalAutoConnectionTest::testMemberFunctionConnection() +{ + QScopedPointer test1(new TestClass()); + QScopedPointer test2(new TestClass()); + KisSignalAutoConnectionsStore conn; + conn.addConnection(test1.data(), &TestClass::sigTest1, test2.data(), &TestClass::slotTest1); + emit test1->sigTest1(); + QVERIFY(test2->m_test1Called); + test2->m_test1Called = false; + conn.clear(); + emit test1->sigTest1(); + QVERIFY(test2->m_test1Called == false); +} + +void KisSignalAutoConnectionTest::testOverloadConnection() +{ + QScopedPointer test1(new TestClass()); + QScopedPointer test2(new TestClass()); + KisSignalAutoConnectionsStore conn; + conn.addConnection(test1.data(), QOverload::of(&TestClass::sigTest2), + test2.data(), QOverload::of(&TestClass::slotTest2)); + conn.addConnection(test1.data(), SIGNAL(sigTest2(int)), test2.data(), SLOT(slotTest2(int))); + emit test1->sigTest2("foo", "bar"); + QVERIFY(test2->m_str1 == "foo"); + QVERIFY(test2->m_str2 == "bar"); + emit test1->sigTest2(5); + QVERIFY(test2->m_number == 5); + conn.clear(); + emit test1->sigTest2("1", "2"); + QVERIFY(test2->m_str1 == "foo"); + QVERIFY(test2->m_str2 == "bar"); + conn.addConnection(test1.data(), SIGNAL(sigTest2(const QString &, const QString &)), + test2.data(), SLOT(slotTest2(const QString &))); + emit test1->sigTest2("3", "4"); + QVERIFY(test2->m_str1 == "3"); + QVERIFY(test2->m_str2 == ""); +} + +void KisSignalAutoConnectionTest::testSignalToSignalConnection() +{ + QScopedPointer test1(new TestClass()); + QScopedPointer test2(new TestClass()); + KisSignalAutoConnectionsStore conn; + conn.addConnection(test1.data(), QOverload::of(&TestClass::sigTest2), + test2.data(), QOverload::of(&TestClass::sigTest2)); + conn.addConnection(test2.data(), SIGNAL(sigTest2(int)), test2.data(), SLOT(slotTest2(int))); + emit test1->sigTest2(10); + QVERIFY(test2->m_number == 10); + conn.clear(); + conn.addConnection(test1.data(), SIGNAL(sigTest2(int)), test2.data(), SIGNAL(sigTest2(int))); + conn.addConnection(test2.data(), QOverload::of(&TestClass::sigTest2), + test2.data(), QOverload::of(&TestClass::slotTest2)); + emit test1->sigTest2(50); + QVERIFY(test2->m_number == 50); +} + +void KisSignalAutoConnectionTest::testDestroyedObject() +{ + QScopedPointer test1(new TestClass()); + QScopedPointer test2(new TestClass()); + KisSignalAutoConnectionsStore conn; + conn.addConnection(test1.data(), QOverload::of(&TestClass::sigTest2), + test2.data(), QOverload::of(&TestClass::slotTest2)); + emit test1->sigTest2(10); + QVERIFY(test2->m_number == 10); + test2.reset(0); + conn.clear(); +} + +TestClass::TestClass(QObject *parent) + : QObject(parent) + , m_test1Called(false) + , m_str1() + , m_str2() + , m_number(0) +{ +} + +TestClass::~TestClass() +{ +} + +void TestClass::slotTest1() +{ + m_test1Called = true; +} + +void TestClass::slotTest2(const QString &arg1, const QString &arg2) +{ + m_str1 = arg1; + m_str2 = arg2; +} + +void TestClass::slotTest2(const QString &arg) +{ + m_str1 = arg; + m_str2 = QString(); +} + +void TestClass::slotTest2(int arg) +{ + m_number = arg; +} + +QTEST_MAIN(KisSignalAutoConnectionTest) diff --git a/libs/global/tests/KisSignalAutoConnectionTest.h b/libs/global/tests/KisSignalAutoConnectionTest.h new file mode 100644 index 0000000000..9c5068dfbd --- /dev/null +++ b/libs/global/tests/KisSignalAutoConnectionTest.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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_SIGNAL_AUTO_CONNECTION_TEST_H_ +#define KIS_SIGNAL_AUTO_CONNECTION_TEST_H_ + +#include + +class KisSignalAutoConnectionTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testMacroConnection(); + void testMemberFunctionConnection(); + void testOverloadConnection(); + void testSignalToSignalConnection(); + void testDestroyedObject(); +}; + +class TestClass : public QObject +{ + Q_OBJECT +public: + TestClass(QObject *parent = 0); + ~TestClass() override; + +Q_SIGNALS: + void sigTest1(); + void sigTest2(const QString &arg1, const QString &arg2); + void sigTest2(int arg); + +public Q_SLOTS: + void slotTest1(); + void slotTest2(const QString &arg1, const QString &arg2); + void slotTest2(const QString &arg); + void slotTest2(int arg); + +public: + bool m_test1Called; + QString m_str1; + QString m_str2; + int m_number; +}; + +#endif // KIS_SIGNAL_AUTO_CONNECTION_TEST_H_ diff --git a/libs/image/floodfill/kis_fill_interval_map.cpp b/libs/image/floodfill/kis_fill_interval_map.cpp index 3b93468ac5..05ea720160 100644 --- a/libs/image/floodfill/kis_fill_interval_map.cpp +++ b/libs/image/floodfill/kis_fill_interval_map.cpp @@ -1,166 +1,174 @@ /* * Copyright (c) 2014 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_fill_interval_map.h" #include "kis_fill_interval_map_p.h" #include "kis_fill_sanity_checks.h" KisFillIntervalMap::KisFillIntervalMap() : m_d(new Private) { } KisFillIntervalMap::~KisFillIntervalMap() { } void KisFillIntervalMap::insertInterval(const KisFillInterval &interval) { Private::GlobalMap::iterator rowMap = m_d->map.find(interval.row); if (rowMap == m_d->map.end()) { rowMap = m_d->map.insert(interval.row, Private::LineIntervalMap()); } rowMap->insert(interval.start, interval); } void KisFillIntervalMap::cropInterval(KisFillInterval *interval) { Private::IteratorRange range; range = m_d->findFirstIntersectingInterval(*interval); Private::LineIntervalMap::iterator it = range.beginIt; while (interval->isValid() && it != range.endIt) { bool needsIncrement = true; if (it->start <= interval->start && it->end >= interval->start) { int savedIntervalStart = interval->start; interval->start = it->end + 1; /** * It might happen that we need to split a backward * interval into two pieces */ if (it->end > interval->end) { KisFillInterval newInterval(interval->end + 1, it->end, it->row); range.rowMapIt->insert(newInterval.start, newInterval); } it->end = savedIntervalStart - 1; /** * It might also happen that the backward interval is * fully eaten by the forward interval. This is possible * only in case the BW-interval was generated by the * strictly adjacent FW-interval, that is (it->start == * interval->start) */ if (!it->isValid()) { it = range.rowMapIt->erase(it); needsIncrement = false; } } else if (it->start <= interval->end && it->end >= interval->end) { int savedIntervalEnd = interval->end; interval->end = it->start - 1; it->start = savedIntervalEnd + 1; /** * The BW-interval is eaten by the FW-interval. See a * comment above */ if (!it->isValid()) { it = range.rowMapIt->erase(it); needsIncrement = false; } } else if (it->start > interval->end) { break; } #ifdef ENABLE_FILL_SANITY_CHECKS else if (it->start > interval->start && it->end < interval->end) { SANITY_ASSERT_MSG(0, "FATAL: The backward interval cannot fully reside inside the forward interval"); - it->invalidate(); interval->invalidate(); + it->invalidate(); + it = range.rowMapIt->erase(it); + needsIncrement = false; } - SANITY_ASSERT_MSG(it == range.endIt || it->isValid(), "FATAL: The backward interval cannot become invalid during the crop action"); - + // The code above should have removed the invalidated backward interval, + // just verify that + KIS_SAFE_ASSERT_RECOVER((it == range.endIt || it->isValid()) && + "FATAL: The backward interval cannot become " + "invalid during the crop action") { + it = range.rowMapIt->erase(it); + needsIncrement = false; + } #endif /* ENABLE_FILL_SANITY_CHECKS */ if (needsIncrement) { it++; } } } KisFillIntervalMap::Private::IteratorRange KisFillIntervalMap::Private::findFirstIntersectingInterval(const KisFillInterval &interval) { Private::GlobalMap::iterator rowMap = map.find(interval.row); if (rowMap == map.end()) { return IteratorRange(); } LineIntervalMap::iterator it = rowMap->begin(); LineIntervalMap::iterator end = rowMap->end(); while(it != end) { if (it->end < interval.start) { ++it; } else if (it->start > interval.end) { it = end; break; } else { break; } } return IteratorRange(it, end, rowMap); } QStack KisFillIntervalMap::fetchAllIntervals(int rowCorrection) const { QStack intervals; Private::GlobalMap::const_iterator rowMapIt = m_d->map.constBegin(); Private::GlobalMap::const_iterator rowMapEndIt = m_d->map.constEnd(); while (rowMapIt != rowMapEndIt) { Private::LineIntervalMap::const_iterator it = rowMapIt->constBegin(); Private::LineIntervalMap::const_iterator end = rowMapIt->constEnd(); while(it != end) { KisFillInterval interval = *it; interval.row += rowCorrection; intervals.append(interval); ++it; } ++rowMapIt; } return intervals; } void KisFillIntervalMap::clear() { m_d->map.clear(); } diff --git a/libs/image/floodfill/kis_fill_sanity_checks.h b/libs/image/floodfill/kis_fill_sanity_checks.h index 49d47efb12..2de42c9bda 100644 --- a/libs/image/floodfill/kis_fill_sanity_checks.h +++ b/libs/image/floodfill/kis_fill_sanity_checks.h @@ -1,41 +1,41 @@ /* * Copyright (c) 2014 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_FILL_SANITY_CHECKS_H #define __KIS_FILL_SANITY_CHECKS_H #define ENABLE_FILL_SANITY_CHECKS //#define ENABLE_CHECKS_FOR_TESTING #ifdef ENABLE_FILL_SANITY_CHECKS #ifdef ENABLE_CHECKS_FOR_TESTING #include #define SANITY_ASSERT_MSG(cond, msg) ((!(cond)) ? throw std::invalid_argument(msg) : qt_noop()) #else -#define SANITY_ASSERT_MSG(cond, msg) Q_ASSERT((cond)) +#define SANITY_ASSERT_MSG(cond, msg) KIS_SAFE_ASSERT_RECOVER_NOOP((cond)) #endif /* ENABLE_CHECKS_FOR_TESTING */ #else #define SANITY_ASSERT_MSG(cond, msg) #endif /* ENABLE_FILL_SANITY_CHECKS */ #endif /* __KIS_FILL_SANITY_CHECKS_H */ diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index db91b1ce07..750d827c09 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,728 +1,732 @@ /* * Copyright (c) 2014 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_scanline_fill.h" #include #include #include #include #include #include "kis_image.h" #include "kis_fill_interval_map.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_fill_sanity_checks.h" template class CopyToSelection : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(0, 0); } public: void setDestinationSelection(KisPaintDeviceSP pixelSelection) { m_pixelSelection = pixelSelection; m_it = m_pixelSelection->createRandomAccessorNG(0,0); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); *m_it->rawData() = opacity; } private: KisPaintDeviceSP m_pixelSelection; KisRandomAccessorSP m_it; }; template class FillWithColor : public BaseClass { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomAccessorNG(0, 0); } public: FillWithColor() : m_pixelSize(0) {} void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(x); Q_UNUSED(y); if (opacity == MAX_SELECTED) { memcpy(dstPtr, m_data, m_pixelSize); } } private: KoColor m_sourceColor; const quint8 *m_data; int m_pixelSize; }; template class FillWithColorExternal : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(0, 0); } public: void setDestinationDevice(KisPaintDeviceSP device) { m_externalDevice = device; m_it = m_externalDevice->createRandomAccessorNG(0,0); } void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); if (opacity == MAX_SELECTED) { memcpy(m_it->rawData(), m_data, m_pixelSize); } } private: KisPaintDeviceSP m_externalDevice; KisRandomAccessorSP m_it; KoColor m_sourceColor; const quint8 *m_data; int m_pixelSize; }; class DifferencePolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { return 0; } return quint8_MAX; } else { return m_colorSpace->difference(m_srcPixelPtr, pixelPtr); } } private: const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class DifferencePolicyOptimized { typedef SrcPixelType HashKeyType; typedef QHash HashType; public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { HashKeyType key = *reinterpret_cast(pixelPtr); quint8 result; typename HashType::iterator it = m_differences.find(key); if (it != m_differences.end()) { result = *it; } else { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { result = 0; } else { result = quint8_MAX; } } else { result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr); } m_differences.insert(key, result); } return result; } private: HashType m_differences; const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class PixelFiller> class SelectionPolicy : public PixelFiller { public: typename PixelFiller::SourceAccessorType m_srcIt; public: SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) : m_threshold(threshold) { this->initDifferences(device, srcPixel, threshold); m_srcIt = this->createSourceDeviceAccessor(device); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { quint8 diff = this->calculateDifference(pixelPtr); if (!useSmoothSelection) { return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } else { quint8 selectionValue = qMax(0, m_threshold - diff); quint8 result = MIN_SELECTED; if (selectionValue > 0) { qreal selectionNorm = qreal(selectionValue) / m_threshold; result = MAX_SELECTED * selectionNorm; } return result; } } private: int m_threshold; }; class IsNonNullPolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(srcPixel); m_pixelSize = device->pixelSize(); m_testPixel.resize(m_pixelSize); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) { return 0; } return quint8_MAX; } private: int m_pixelSize; QByteArray m_testPixel; }; template class IsNonNullPolicyOptimized { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(device); Q_UNUSED(srcPixel); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { SrcPixelType *pixel = reinterpret_cast(pixelPtr); return *pixel == 0; } }; class GroupSplitPolicy { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType m_srcIt; public: GroupSplitPolicy(KisPaintDeviceSP scribbleDevice, KisPaintDeviceSP groupMapDevice, qint32 groupIndex, quint8 referenceValue, int threshold) : m_threshold(threshold), m_groupIndex(groupIndex), m_referenceValue(referenceValue) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0); m_srcIt = scribbleDevice->createRandomAccessorNG(0,0); m_groupMapIt = groupMapDevice->createRandomAccessorNG(0,0); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { // TODO: either threshold should always be null, or there should be a special // case for *pixelPtr == 0, which is different from all the other groups, // whatever the threshold is int diff = qAbs(int(*pixelPtr) - m_referenceValue); return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(opacity); // erase the scribble *dstPtr = 0; // write group index into the map m_groupMapIt->moveTo(x, y); qint32 *groupMapPtr = reinterpret_cast(m_groupMapIt->rawData()); if (*groupMapPtr != 0) { dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex); } KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0); *groupMapPtr = m_groupIndex; } private: int m_threshold; qint32 m_groupIndex; quint8 m_referenceValue; KisRandomAccessorSP m_groupMapIt; }; struct Q_DECL_HIDDEN KisScanlineFill::Private { KisPaintDeviceSP device; KisRandomAccessorSP it; QPoint startPoint; QRect boundingRect; int threshold; int rowIncrement; KisFillIntervalMap backwardMap; QStack forwardStack; inline void swapDirection() { rowIncrement *= -1; - SANITY_ASSERT_MSG(forwardStack.isEmpty(), - "FATAL: the forward stack must be empty " - "on a direction swap"); + KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() && + "FATAL: the forward stack must be empty " + "on a direction swap"); forwardStack = QStack(backwardMap.fetchAllIntervals(rowIncrement)); backwardMap.clear(); } }; KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect) : m_d(new Private) { m_d->device = device; m_d->it = device->createRandomAccessorNG(startPoint.x(), startPoint.y()); m_d->startPoint = startPoint; m_d->boundingRect = boundingRect; m_d->rowIncrement = 1; m_d->threshold = 0; } KisScanlineFill::~KisScanlineFill() { } void KisScanlineFill::setThreshold(int threshold) { m_d->threshold = threshold; } template void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy) { int x; int endX; int columnIncrement; int *intervalBorder; int *backwardIntervalBorder; KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow); if (extendRight) { x = currentInterval->end; endX = m_d->boundingRect.right(); if (x >= endX) return; columnIncrement = 1; intervalBorder = ¤tInterval->end; backwardInterval.start = currentInterval->end + 1; backwardIntervalBorder = &backwardInterval.end; } else { x = currentInterval->start; endX = m_d->boundingRect.left(); if (x <= endX) return; columnIncrement = -1; intervalBorder = ¤tInterval->start; backwardInterval.end = currentInterval->start - 1; backwardIntervalBorder = &backwardInterval.start; } do { x += columnIncrement; pixelPolicy.m_srcIt->moveTo(x, srcRow); quint8 *pixelPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { *intervalBorder = x; *backwardIntervalBorder = x; pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow); } else { break; } } while (x != endX); if (backwardInterval.isValid()) { m_d->backwardMap.insertInterval(backwardInterval); } } template void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy) { m_d->backwardMap.cropInterval(&interval); if (!interval.isValid()) return; int firstX = interval.start; int lastX = interval.end; int x = firstX; int row = interval.row; int nextRow = row + rowIncrement; KisFillInterval currentForwardInterval; int numPixelsLeft = 0; quint8 *dataPtr = 0; const int pixelSize = m_d->device->pixelSize(); while(x <= lastX) { // a bit of optimzation for not calling slow random accessor // methods too often if (numPixelsLeft <= 0) { pixelPolicy.m_srcIt->moveTo(x, row); numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1; dataPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); } else { numPixelsLeft--; dataPtr += pixelSize; } quint8 *pixelPtr = dataPtr; quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { if (!currentForwardInterval.isValid()) { currentForwardInterval.start = x; currentForwardInterval.end = x; currentForwardInterval.row = nextRow; } else { currentForwardInterval.end = x; } pixelPolicy.fillPixel(pixelPtr, opacity, x, row); if (x == firstX) { extendedPass(¤tForwardInterval, row, false, pixelPolicy); } if (x == lastX) { extendedPass(¤tForwardInterval, row, true, pixelPolicy); } } else { if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); currentForwardInterval.invalidate(); } } x++; } if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); } } template void KisScanlineFill::runImpl(T &pixelPolicy) { KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty()); KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y()); m_d->forwardStack.push(startInterval); /** * In the end of the first pass we should add an interval * containing the starting pixel, but directed into the opposite * direction. We cannot do it in the very beginning because the * intervals are offset by 1 pixel during every swap operation. */ bool firstPass = true; while (!m_d->forwardStack.isEmpty()) { while (!m_d->forwardStack.isEmpty()) { KisFillInterval interval = m_d->forwardStack.pop(); if (interval.row > m_d->boundingRect.bottom() || interval.row < m_d->boundingRect.top()) { continue; } processLine(interval, m_d->rowIncrement, pixelPolicy); } m_d->swapDirection(); if (firstPass) { startInterval.row--; m_d->forwardStack.push(startInterval); firstPass = false; } } } -void KisScanlineFill::fillColor(const KoColor &fillColor) +void KisScanlineFill::fillColor(const KoColor &originalFillColor) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); + KoColor fillColor(originalFillColor); + fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } } -void KisScanlineFill::fillColor(const KoColor &fillColor, KisPaintDeviceSP externalDevice) +void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); + KoColor fillColor(originalFillColor); + fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } } void KisScanlineFill::clearNonZeroComponent() { const int pixelSize = m_d->device->pixelSize(); KoColor srcColor(Qt::transparent, m_d->device->colorSpace()); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } } void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1); KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4); KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); const quint8 referenceValue = *it->rawDataConst(); GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold); runImpl(policy); } void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval) { KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace()); KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace()); SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); processLine(processInterval, 1, policy); } QVector KisScanlineFill::testingGetForwardIntervals() const { return QVector(m_d->forwardStack); } KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const { return &m_d->backwardMap; } diff --git a/libs/image/kis_base_rects_walker.h b/libs/image/kis_base_rects_walker.h index 77e7c66740..dc6aaa1684 100644 --- a/libs/image/kis_base_rects_walker.h +++ b/libs/image/kis_base_rects_walker.h @@ -1,510 +1,517 @@ /* * 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_BASE_RECTS_WALKER_H #define __KIS_BASE_RECTS_WALKER_H #include #include "kis_layer.h" #include "kis_abstract_projection_plane.h" #include "kis_projection_leaf.h" class KisBaseRectsWalker; typedef KisSharedPtr KisBaseRectsWalkerSP; class KRITAIMAGE_EXPORT KisBaseRectsWalker : public KisShared { public: enum UpdateType { UPDATE, UPDATE_NO_FILTHY, FULL_REFRESH, UNSUPPORTED }; typedef qint32 NodePosition; enum NodePositionValues { /** * There are two different sets of values. * The first describes the position of the node to the graph, * the second shows the position to the filthy node */ N_NORMAL = 0x00, N_TOPMOST = 0x01, N_BOTTOMMOST = 0x02, N_EXTRA = 0x04, N_ABOVE_FILTHY = 0x08, N_FILTHY_ORIGINAL = 0x10, // not used actually N_FILTHY_PROJECTION = 0x20, N_FILTHY = 0x40, N_BELOW_FILTHY = 0x80 }; #define GRAPH_POSITION_MASK 0x07 static inline KisNode::PositionToFilthy convertPositionToFilthy(NodePosition position) { static const int positionToFilthyMask = N_ABOVE_FILTHY | N_FILTHY_PROJECTION | N_FILTHY | N_BELOW_FILTHY; qint32 positionToFilthy = position & N_EXTRA ? N_FILTHY : position & positionToFilthyMask; // We do not use N_FILTHY_ORIGINAL yet, so... Q_ASSERT(positionToFilthy); return static_cast(positionToFilthy); } struct CloneNotification { CloneNotification() {} CloneNotification(KisNodeSP node, const QRect &dirtyRect) : m_layer(qobject_cast(node.data())), m_dirtyRect(dirtyRect) {} void notify() { Q_ASSERT(m_layer); // clones are possible for layers only m_layer->updateClones(m_dirtyRect); } private: friend class KisWalkersTest; KisLayerSP m_layer; QRect m_dirtyRect; }; typedef QVector CloneNotificationsVector; struct JobItem { KisProjectionLeafSP m_leaf; NodePosition m_position; /** * The rect that should be prepared on this node. * E.g. area where the filter applies on filter layer * or an area of a paint layer that will be copied to * the projection. */ QRect m_applyRect; }; typedef QStack LeafStack; public: KisBaseRectsWalker() : m_levelOfDetail(0) { } virtual ~KisBaseRectsWalker() { } void collectRects(KisNodeSP node, const QRect& requestedRect) { clear(); KisProjectionLeafSP startLeaf = node->projectionLeaf(); m_nodeChecksum = calculateChecksum(startLeaf, requestedRect); m_graphChecksum = node->graphSequenceNumber(); m_resultChangeRect = requestedRect; m_resultUncroppedChangeRect = requestedRect; m_requestedRect = requestedRect; m_startNode = node; m_levelOfDetail = getNodeLevelOfDetail(startLeaf); startTrip(startLeaf); } inline void recalculate(const QRect& requestedRect) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_startNode); KisProjectionLeafSP startLeaf = m_startNode->projectionLeaf(); int calculatedLevelOfDetail = getNodeLevelOfDetail(startLeaf); if (m_levelOfDetail != calculatedLevelOfDetail) { qWarning() << "WARNING: KisBaseRectsWalker::recalculate()" << "The levelOfDetail has changes with time," << "which couldn't have happened!" << ppVar(m_levelOfDetail) << ppVar(calculatedLevelOfDetail); m_levelOfDetail = calculatedLevelOfDetail; } if(startLeaf->isStillInGraph()) { collectRects(m_startNode, requestedRect); } else { clear(); m_nodeChecksum = calculateChecksum(startLeaf, requestedRect); m_graphChecksum = m_startNode->graphSequenceNumber(); m_resultChangeRect = QRect(); m_resultUncroppedChangeRect = QRect(); } } bool checksumValid() { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_startNode, false); return m_nodeChecksum == calculateChecksum(m_startNode->projectionLeaf(), m_requestedRect) && m_graphChecksum == m_startNode->graphSequenceNumber(); } inline void setCropRect(QRect cropRect) { m_cropRect = cropRect; } inline QRect cropRect() const{ return m_cropRect; } // return a reference for efficiency reasons inline LeafStack& leafStack() { return m_mergeTask; } // return a reference for efficiency reasons inline CloneNotificationsVector& cloneNotifications() { return m_cloneNotifications; } inline QRect accessRect() const { return m_resultAccessRect; } inline QRect changeRect() const { return m_resultChangeRect; } inline QRect uncroppedChangeRect() const { return m_resultUncroppedChangeRect; } inline bool needRectVaries() const { return m_needRectVaries; } inline bool changeRectVaries() const { return m_changeRectVaries; } inline KisNodeSP startNode() const { return m_startNode; } inline QRect requestedRect() const { return m_requestedRect; } inline int levelOfDetail() const { return m_levelOfDetail; } virtual UpdateType type() const = 0; protected: /** * Initiates collecting of rects. * Should be implemented in derived classes */ virtual void startTrip(KisProjectionLeafSP startWith) = 0; protected: static inline qint32 getGraphPosition(qint32 position) { return position & GRAPH_POSITION_MASK; } static inline bool hasClones(KisNodeSP node) { KisLayer *layer = qobject_cast(node.data()); return layer && layer->hasClones(); } static inline NodePosition calculateNodePosition(KisProjectionLeafSP leaf) { KisProjectionLeafSP nextLeaf = leaf->nextSibling(); while(nextLeaf && !nextLeaf->isLayer()) nextLeaf = nextLeaf->nextSibling(); if (!nextLeaf) return N_TOPMOST; KisProjectionLeafSP prevLeaf = leaf->prevSibling(); while(prevLeaf && !prevLeaf->isLayer()) prevLeaf = prevLeaf->prevSibling(); if (!prevLeaf) return N_BOTTOMMOST; return N_NORMAL; } inline bool isStartLeaf(KisProjectionLeafSP leaf) const { return leaf->node() == m_startNode; } inline void clear() { m_resultAccessRect = m_resultNeedRect = /*m_resultChangeRect =*/ m_childNeedRect = m_lastNeedRect = QRect(); m_needRectVaries = m_changeRectVaries = false; m_mergeTask.clear(); m_cloneNotifications.clear(); // Not needed really. Think over removing. //m_startNode = 0; //m_requestedRect = QRect(); } inline void pushJob(KisProjectionLeafSP leaf, NodePosition position, QRect applyRect) { JobItem item = {leaf, position, applyRect}; m_mergeTask.push(item); } inline QRect cropThisRect(const QRect& rect) { return m_cropRect.isValid() ? rect & m_cropRect : rect; } /** * Used by KisFullRefreshWalker as it has a special changeRect strategy */ inline void setExplicitChangeRect(const QRect &changeRect, bool changeRectVaries) { m_resultChangeRect = changeRect; m_resultUncroppedChangeRect = changeRect; m_changeRectVaries = changeRectVaries; } /** * Called for every node we meet on a forward way of the trip. */ virtual void registerChangeRect(KisProjectionLeafSP leaf, NodePosition position) { // We do not work with masks here. It is KisLayer's job. if(!leaf->isLayer()) return; if(!(position & N_FILTHY) && !leaf->visible()) return; QRect currentChangeRect = leaf->projectionPlane()->changeRect(m_resultChangeRect, convertPositionToFilthy(position)); currentChangeRect = cropThisRect(currentChangeRect); if(!m_changeRectVaries) m_changeRectVaries = currentChangeRect != m_resultChangeRect; m_resultChangeRect = currentChangeRect; m_resultUncroppedChangeRect = leaf->projectionPlane()->changeRect(m_resultUncroppedChangeRect, convertPositionToFilthy(position)); registerCloneNotification(leaf->node(), position); } void registerCloneNotification(KisNodeSP node, NodePosition position) { /** * Note, we do not check for (N_ABOVE_FILTHY && * dependOnLowerNodes(node)) because it may lead to an * infinite loop with filter layer. Activate it when it is * guaranteed that it is not possible to create a filter layer * avobe its own clone */ if(hasClones(node) && position & (N_FILTHY | N_FILTHY_PROJECTION | N_EXTRA)) { m_cloneNotifications.append( CloneNotification(node, m_resultUncroppedChangeRect)); } } /** * Called for every node we meet on a backward way of the trip. */ virtual void registerNeedRect(KisProjectionLeafSP leaf, NodePosition position) { // We do not work with masks here. It is KisLayer's job. if(!leaf->isLayer()) return; if(m_mergeTask.isEmpty()) m_resultAccessRect = m_resultNeedRect = m_childNeedRect = m_lastNeedRect = m_resultChangeRect; if (leaf->parent() && position & N_TOPMOST) { bool parentNeedRectFound = false; QRect parentNeedRect; Q_FOREACH(const JobItem &job, m_mergeTask) { if (job.m_leaf == leaf->parent()) { parentNeedRect = job.m_leaf->projectionPlane()->needRectForOriginal(job.m_applyRect); parentNeedRectFound = true; } } // TODO: check if we can put this requirement // KIS_SAFE_ASSERT_RECOVER_NOOP(parentNeedRectFound); if (parentNeedRectFound) { m_lastNeedRect = parentNeedRect; } else { // legacy way of fetching parent need rect, just // takes need rect of the last visited filthy node m_lastNeedRect = m_childNeedRect; } } if (!leaf->visible()) { if (!m_lastNeedRect.isEmpty()) { // push a dumb job to fit state machine requirements pushJob(leaf, position, m_lastNeedRect); } } else if(position & (N_FILTHY | N_ABOVE_FILTHY | N_EXTRA)) { if(!m_lastNeedRect.isEmpty()) pushJob(leaf, position, m_lastNeedRect); //else /* Why push empty rect? */; m_resultAccessRect |= leaf->projectionPlane()->accessRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = leaf->projectionPlane()->needRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = cropThisRect(m_lastNeedRect); m_childNeedRect = m_lastNeedRect; } else if(position & (N_BELOW_FILTHY | N_FILTHY_PROJECTION)) { if(!m_lastNeedRect.isEmpty()) { pushJob(leaf, position, m_lastNeedRect); m_resultAccessRect |= leaf->projectionPlane()->accessRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = leaf->projectionPlane()->needRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = cropThisRect(m_lastNeedRect); } } else { // N_FILTHY_ORIGINAL is not used so it goes there qFatal("KisBaseRectsWalker: node position(%d) is out of range", position); } if(!m_needRectVaries) m_needRectVaries = m_resultNeedRect != m_lastNeedRect; m_resultNeedRect |= m_lastNeedRect; } virtual void adjustMasksChangeRect(KisProjectionLeafSP firstMask) { KisProjectionLeafSP currentLeaf = firstMask; while (currentLeaf) { /** * ATTENTION: we miss the first mask */ do { currentLeaf = currentLeaf->nextSibling(); } while (currentLeaf && (!currentLeaf->isMask() || !currentLeaf->visible())); if(currentLeaf) { QRect changeRect = currentLeaf->projectionPlane()->changeRect(m_resultChangeRect); m_changeRectVaries |= changeRect != m_resultChangeRect; m_resultChangeRect = changeRect; m_resultUncroppedChangeRect = changeRect; } } KisProjectionLeafSP parentLayer = firstMask->parent(); KIS_SAFE_ASSERT_RECOVER_RETURN(parentLayer); registerCloneNotification(parentLayer->node(), N_FILTHY_PROJECTION); } static qint32 calculateChecksum(KisProjectionLeafSP leaf, const QRect &requestedRect) { qint32 checksum = 0; qint32 x, y, w, h; QRect tempRect; tempRect = leaf->projectionPlane()->changeRect(requestedRect); tempRect.getRect(&x, &y, &w, &h); checksum += -x - y + w + h; tempRect = leaf->projectionPlane()->needRect(requestedRect); tempRect.getRect(&x, &y, &w, &h); checksum += -x - y + w + h; // errKrita << leaf << requestedRect << "-->" << checksum; return checksum; } private: inline int getNodeLevelOfDetail(KisProjectionLeafSP leaf) { - while (!leaf->projection()) { + while (leaf && !leaf->projection()) { leaf = leaf->parent(); } - KIS_ASSERT_RECOVER(leaf->projection()) { + if (!leaf || !leaf->projection()) { + /** + * Such errors may happen during undo or too quick node removal, + * they shouldn't cause any real problems in Krita work. + */ + qWarning() << "WARNING: KisBaseRectsWalker::getNodeLevelOfDetail() " + "failed to fetch currentLevelOfDetail() from the node. " + "Perhaps the node was removed from the image in the meantime."; return 0; } return leaf->projection()->defaultBounds()->currentLevelOfDetail(); } private: /** * The result variables. * By the end of a recursion they will store a complete * data for a successful merge operation. */ QRect m_resultAccessRect; QRect m_resultNeedRect; QRect m_resultChangeRect; QRect m_resultUncroppedChangeRect; bool m_needRectVaries; bool m_changeRectVaries; LeafStack m_mergeTask; CloneNotificationsVector m_cloneNotifications; /** * Used by update optimization framework */ KisNodeSP m_startNode; QRect m_requestedRect; /** * Used for getting know whether the start node * properties have changed since the walker was * calculated */ qint32 m_nodeChecksum; /** * Used for getting know whether the structure of * the graph has changed since the walker was * calculated */ qint32 m_graphChecksum; /** * Temporary variables */ QRect m_cropRect; QRect m_childNeedRect; QRect m_lastNeedRect; int m_levelOfDetail; }; #endif /* __KIS_BASE_RECTS_WALKER_H */ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 04a3ff5d21..cace3629d0 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1913 +1,1961 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 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 "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "tiles3/kis_lockless_stack.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisLocklessStack savedDisabledUIUpdates; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool allowMasksOnRootNode = false; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } -KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) - : KisNodeFacade(), - KisNodeGraphListener(), - KisShared(), - m_d(new KisImagePrivate(this, - rhs.width(), rhs.height(), - rhs.colorSpace(), - undoStore ? undoStore : new KisDumbUndoStore(), - new KisImageAnimationInterface(*rhs.animationInterface(), this))) +void KisImage::copyFromImage(const KisImage &rhs) { - // make sure KisImage belongs to the GUI thread - moveToThread(qApp->thread()); - connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); + copyFromImageImpl(rhs, REPLACE); +} + +void KisImage::copyFromImageImpl(const KisImage &rhs, int policy) +{ + // make sure we choose exactly one from REPLACE and CONSTRUCT + KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT)); + + // only when replacing do we need to emit signals +#define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit + if (policy & REPLACE) { // if we are constructing the image, these are already set + if (m_d->width != rhs.width() || m_d->height != rhs.height()) { + m_d->width = rhs.width(); + m_d->height = rhs.height(); + emit sigSizeChanged(QPointF(), QPointF()); + } + if (m_d->colorSpace != rhs.colorSpace()) { + m_d->colorSpace = rhs.colorSpace(); + emit sigColorSpaceChanged(m_d->colorSpace); + } + } + + // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool) setObjectName(rhs.objectName()); - m_d->xres = rhs.m_d->xres; - m_d->yres = rhs.m_d->yres; + if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) { + m_d->xres = rhs.m_d->xres; + m_d->yres = rhs.m_d->yres; + EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres); + } m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode; if (rhs.m_d->proofingConfig) { - m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); + KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); + if (policy & REPLACE) { + setProofingConfiguration(proofingConfig); + } else { + m_d->proofingConfig = proofingConfig; + } } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); + bool exactCopy = policy & EXACT_COPY; + if (exactCopy || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), - [&linearizedNodes](KisNodeSP node) { - linearizedNodes.enqueue(node); - }); + [&linearizedNodes](KisNodeSP node) { + linearizedNodes.enqueue(node); + }); KisLayerUtils::recursiveApplyNodes(newRoot, - [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { - KisNodeSP refNode = linearizedNodes.dequeue(); - - if (exactCopy) { - node->setUuid(refNode->uuid()); - } + [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { + KisNodeSP refNode = linearizedNodes.dequeue(); - if (rhs.m_d->isolatedRootNode && - rhs.m_d->isolatedRootNode == refNode) { + if (exactCopy) { + node->setUuid(refNode->uuid()); + } - m_d->isolatedRootNode = node; - } - }); + if (rhs.m_d->isolatedRootNode && + rhs.m_d->isolatedRootNode == refNode) { + m_d->isolatedRootNode = node; + } + }); } + KisLayerUtils::recursiveApplyNodes(newRoot, + [](KisNodeSP node) { + dbgImage << "Node: " << (void *)node.data(); + }); + + m_d->compositions.clear(); + Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } - rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); + EMIT_IF_NEEDED sigLayersChangedAsync(); + + m_d->nserver = rhs.m_d->nserver; vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; /** * The overlay device is not inherited when cloning the image! */ if (rhs.m_d->overlaySelectionMask) { const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); m_d->rootLayer->setDirty(dirtyRect); } +#undef EMIT_IF_NEEDED +} + +KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) + : KisNodeFacade(), + KisNodeGraphListener(), + KisShared(), + m_d(new KisImagePrivate(this, + rhs.width(), rhs.height(), + rhs.colorSpace(), + undoStore ? undoStore : new KisDumbUndoStore(), + new KisImageAnimationInterface(*rhs.animationInterface(), this))) +{ + // make sure KisImage belongs to the GUI thread + moveToThread(qApp->thread()); + connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); + + copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0)); } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data()) && deletedNode == m_d->isolatedRootNode) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; QPointF offset; { KisTransformWorker worker(0, scaleX, scaleY, 0, 0, 0, 0, 0.0, 0, 0, 0, 0); QTransform transform = worker.transform(); offset = center - transform.map(center); } KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, offset.x(), offset.y(), filterStrategy); visitor->setSelection(selection); if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection) { // we can either transform (and resize) the whole image or // transform a selection, we cannot do both at the same time KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { selection = 0; } const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != baseBounds.width() || newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0); } void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection) { const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); const QPointF origin = QRectF(baseBounds).center(); //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, 0); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten(KisNodeSP activeNode) { KisLayerUtils::flattenImage(this, activeNode); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); setRoot(m_d->rootLayer.data()); this->setDefaultProjectionColor(defaultProjectionColor); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } QVector KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); QRect rect; QVector postponedUpdates; while (m_d->savedDisabledUIUpdates.pop(rect)) { postponedUpdates.append(rect); } return postponedUpdates; } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } else { m_d->savedDisabledUIUpdates.push(rc); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } void KisImage::setAllowMasksOnRootNode(bool value) { m_d->allowMasksOnRootNode = value; } bool KisImage::allowMasksOnRootNode() const { return m_d->allowMasksOnRootNode; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index db16c755e1..d28a034dfe 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1139 +1,1154 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 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 KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; class KisPaintDevice; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @p colorSpace can be null. In that case, it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); ~KisImage() override; public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; KisNode* graphOverlayNode() const override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); + void copyFromImage(const KisImage &rhs); + +private: + + // must specify exactly one from CONSTRUCT or REPLACE. + enum CopyPolicy { + CONSTRUCT = 1, ///< we are copy-constructing a new KisImage + REPLACE = 2, ///< we are replacing the current KisImage with another + EXACT_COPY = 4, /// we need an exact copy of the original image + }; + + void copyFromImageImpl(const KisImage &rhs, int policy); + +public: + /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; /** * Sets the mask (it must be a part of the node hierarchy already) to be paited on * the top of all layers. This method does all the locking and syncing for you. It * is executed asynchronously. */ void setOverlaySelectionMask(KisSelectionMaskSP mask); /** * \see setOverlaySelectionMask */ KisSelectionMaskSP overlaySelectionMask() const; /** * \see setOverlaySelectionMask */ bool hasOverlaySelectionMask() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param node node to crop * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param center the center of the scaling * @param scaleX x-scale coefficient to be applied to the node * @param scaleY y-scale coefficient to be applied to the node * @param filterStrategy filtering strategy * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @p angleX, @p angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX x-shear given in degrees. * @param angleY y-shear given in degrees. * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * @note No conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * and executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(KisNodeSP activeNode); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is deleted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return current level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; /** * Configure the image to allow masks on the root not (as reported by * root()->allowAsChild()). By default it is not allowed (because it * looks weird from GUI point of view) */ void setAllowMasksOnRootNode(bool value); /** * \see setAllowMasksOnRootNode() */ bool allowMasksOnRootNode() const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. Parameter is the rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. * * The last call to enableUIUpdates() will return the list of udpates * that were requested while they were blocked. */ void disableUIUpdates() override; /** * \see disableUIUpdates */ QVector enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to distinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index 4935b75bb7..ba7e7cab6b 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1556 +1,1557 @@ /* * Copyright (c) 2015 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_layer_utils.h" #include #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "commands/kis_image_change_visibility_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include "commands/kis_node_compositeop_command.h" #include #include "krita_utils.h" #include "kis_image_signal_router.h" namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { - KisLayerSP layer = qobject_cast(node.data()); - KisSelectionMaskSP mask; + Q_FOREACH(KisNodeSP child, node->childNodes(QStringList("KisSelectionMask"), KoProperties())) { - if (layer && (mask = layer->selectionMask())) { - selectionMasks.append(mask); + KisSelectionMaskSP mask = qobject_cast(child.data()); + if (mask) { + selectionMasks.append(mask); + } } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; bool useInTimeline = false; bool enableOnionSkins = false; virtual KisNodeList allSrcNodes() = 0; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(currLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); paintLayer = qobject_cast(prevLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); useInTimeline |= node->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(node.data()); if (paintLayer) { enableOnionSkins |= paintLayer->onionSkinEnabled(); } } } KisNodeList mergedNodes; bool nodesCompositingVaries = false; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand { DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { /** * We disable extra compositing only in case all the layers have * the same compositing properties, therefore, we can just sum them using * Normal blend mode */ if (m_info->nodesCompositingVaries) return; // we should disable dirty requests on **redo only**, otherwise // the state of the layers will not be recovered on undo m_info->image->disableDirtyRequests(); Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (node->compositeOpId() != COMPOSITE_OVER) { addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER)); } if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::inheritAlpha, false); addCommand(new KisNodePropertyListCommand(node, props)); } } m_info->image->enableDirtyRequests(); } private: MergeMultipleInfoSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { forceAllDelayedNodesUpdate(node); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } m_info->dstNode->setUseInTimeline(m_info->useInTimeline); KisPaintLayer *dstPaintLayer = qobject_cast(m_info->dstNode.data()); if (dstPaintLayer) { dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); m_info->dstNode = dstPaintLayer; if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } auto channelFlagsLazy = [](KisNodeSP node) { KisLayer *layer = dynamic_cast(node.data()); return layer ? layer->channelFlags() : QBitArray(); }; QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; bool isFirstCycle = true; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (isFirstCycle) { compositeOpId = node->compositeOpId(); channelFlags = channelFlagsLazy(node); isFirstCycle = false; } else if (compositeOpId != node->compositeOpId() || channelFlags != channelFlagsLazy(node)) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } m_info->nodesCompositingVaries = compositionVaries; m_info->dstNode->setUseInTimeline(m_info->useInTimeline); dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::partB() { KisImageSignalType type; if (getState() == State::FINALIZING) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } SelectGlobalSelectionMask::SelectGlobalSelectionMask(KisImageSP image) : m_image(image) { } void SelectGlobalSelectionMask::redo() { KisImageSignalType type = ComplexNodeReselectionSignal(m_image->rootLayer()->selectionMask(), KisNodeList()); m_image->signalRouter()->emitNotification(type); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); auto isNodeWeird = [] (KisNodeSP node) { const bool normalCompositeMode = node->compositeOpId() == COMPOSITE_OVER; KisLayer *layer = dynamic_cast(node.data()); const bool hasInheritAlpha = layer && layer->alphaChannelDisabled(); return !normalCompositeMode && !hasInheritAlpha; }; while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, !isNodeWeird(node), true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data()) && !node->isFakeNode()) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node // -- checking all parents if they are included in nodesToDelete // Not every descendant is included in nodesToDelete even if in fact // they are going to be deleted, so we need to check it. // If we consider the path from root to the putAfter node, // if there are any nodes marked for deletion, any node afterwards // is going to be deleted, too. // example: root . . . . . ! ! . . ! ! ! ! . . . . putAfter // it should be: root . . . . . ! ! ! ! ! ! ! ! ! ! ! ! !putAfter // and here: root . . . . X ! ! . . ! ! ! ! . . . . putAfter // you can see which node is "the perfect ancestor" // (marked X; called "parent" in the function arguments). // and here: root . . . . . O ! . . ! ! ! ! . . . . putAfter // you can see which node is "the topmost deleted ancestor" (marked 'O') KisNodeSP node = putAfter->parent(); bool foundDeletedAncestor = false; KisNodeSP topmostAncestorToDelete = nullptr; while (node) { if (nodesToDelete.contains(node) && !nodesToDelete.contains(node->parent())) { foundDeletedAncestor = true; topmostAncestorToDelete = node; // Here node is to be deleted and its parent is not, // so its parent is the one of the first not deleted (="perfect") ancestors. // We need the one that is closest to the top (root) } node = node->parent(); } if (foundDeletedAncestor) { parent = topmostAncestorToDelete->parent(); putAfter = topmostAncestorToDelete; } else { parent = putAfter->parent(); // putAfter (and none of its ancestors) is to be deleted, so its parent is the first not deleted ancestor } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { KisNodeSP oldRoot = m_info->image->root(); KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, true, false)); /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } KisNodeList safeNodesToDelete = m_info->allSrcNodes(); for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) { KisNodeSP node = *it; if (node->userLocked() && node->visible()) { addCommand(new KisImageChangeVisibilityCommand(false, node)); } } KritaUtils::filterContainer(safeNodesToDelete, [](KisNodeSP node) { return !node->userLocked(); }); safeRemoveMultipleNodes(safeNodesToDelete, m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::partA() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::partB() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd); applyKeyframeColorLabel(keyframe); addCommand(cmd); } void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) { Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) { Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisKeyframeSP keyframe = channel->keyframeAt(m_frame); if (!keyframe.isNull() && keyframe->colorLabel() != 0) { dstKeyframe->setColorLabel(keyframe->colorLabel()); return; } } } dstKeyframe->setColorLabel(0); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // NOTE: shape layer may have emitted spontaneous jobs during layer creation, // wait for them to complete! applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass through only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { //qDebug() << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible() || node->userLocked()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void filterUnlockedNodes(KisNodeList &nodes) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((*it)->userLocked()) { it = nodes.erase(it); } else { ++it; } } } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { /* If the putAfter node is invisible, * we should instead pick one of the nodes * to be merged to avoid a null putAfter. */ if (!putAfter->visible()){ putAfter = mergedNodes.first(); } applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); applicator.applyCommand(new DisableExtraCompositing(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image, KisNodeSP activeNode) { if (!activeNode) { activeNode = image->root()->lastChild(); } KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, activeNode, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::partB() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } void forceAllDelayedNodesUpdate(KisNodeSP root) { KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } bool hasDelayedNodeWithUpdates(KisNodeSP root) { return recursiveFindNode(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); return delayedUpdate ? delayedUpdate->hasPendingTimedUpdates() : false; }); } KisImageSP findImageByHierarchy(KisNodeSP node) { while (node) { const KisLayer *layer = dynamic_cast(node.data()); if (layer) { return layer->image(); } node = node->parent(); } return 0; } } diff --git a/libs/pigment/KoLabColorSpaceTraits.h b/libs/pigment/KoLabColorSpaceTraits.h index 87cf81c435..925aba8f1c 100644 --- a/libs/pigment/KoLabColorSpaceTraits.h +++ b/libs/pigment/KoLabColorSpaceTraits.h @@ -1,400 +1,400 @@ /* * Copyright (c) 2006-2007 Cyrille Berger * Copyright (c) 2016 L. E. Segovia * * 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 _KO_LAB_COLORSPACE_TRAITS_H_ #define _KO_LAB_COLORSPACE_TRAITS_H_ -/** +/** * LAB traits, it provides some convenient functions to * access LAB channels through an explicit API. * * Use this class in conjonction with KoColorSpace::toLabA16 and * KoColorSpace::fromLabA16 data. * * Example: * quint8* p = KoLabU16Traits::allocate(1); * oneKoColorSpace->toLabA16(somepointertodata, p, 1); * KoLabU16Traits::setL( p, KoLabU16Traits::L(p) / 10 ); * oneKoColorSpace->fromLabA16(p, somepointertodata, 1); */ template struct KoLabTraits : public KoColorSpaceTrait<_channels_type_, 4, 3> { typedef _channels_type_ channels_type; typedef KoColorSpaceTrait<_channels_type_, 4, 3> parent; static const qint32 L_pos = 0; static const qint32 a_pos = 1; static const qint32 b_pos = 2; /** * An Lab pixel */ struct Pixel { channels_type L; channels_type a; channels_type b; channels_type alpha; }; /// @return the L component inline static channels_type L(quint8* data) { channels_type* d = parent::nativeArray(data); return d[L_pos]; } /// Set the L component inline static void setL(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[L_pos] = nv; } /// @return the a component inline static channels_type a(quint8* data) { channels_type* d = parent::nativeArray(data); return d[a_pos]; } /// Set the a component inline static void setA(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[a_pos] = nv; } /// @return the b component inline static channels_type b(quint8* data) { channels_type* d = parent::nativeArray(data); return d[b_pos]; } /// Set the a component inline static void setB(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[b_pos] = nv; } }; //For quint* values must range from 0 to 1 - see KoColorSpaceMaths struct KoLabU8Traits : public KoLabTraits { static const quint32 MAX_CHANNEL_L = 100; static const quint32 MAX_CHANNEL_AB = 255; static const quint32 CHANNEL_AB_ZERO_OFFSET = 128; inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = nativeArray(pixel)[i]; switch (i) { case L_pos: channels[i] = ((qreal)c) / MAX_CHANNEL_L; break; case a_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case b_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case 3: channels[i] = ((qreal)c) / UINT16_MAX; break; default: channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; break; } } } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > parent::channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; switch (channelIndex) { case L_pos: return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); case a_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case b_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case 3: return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); default: return QString("Error"); } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = 0; switch (i) { case L_pos: b = qBound((float)0, (float)MAX_CHANNEL_L * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)0, - (float)MAX_CHANNEL_AB * values[i], + (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET, (float)MAX_CHANNEL_AB); break; default: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; nativeArray(pixel)[i] = c; } } }; struct KoLabU16Traits : public KoLabTraits { // https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c static const quint32 MAX_CHANNEL_L = 0xFF00; static const quint32 MAX_CHANNEL_AB = 0xFFFF; static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000; inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = nativeArray(pixel)[i]; switch (i) { case L_pos: channels[i] = ((qreal)c) / MAX_CHANNEL_L; break; case a_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case b_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case 3: channels[i] = ((qreal)c) / UINT16_MAX; break; default: channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; break; } } } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > parent::channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; switch (channelIndex) { case L_pos: return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); case a_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case b_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case 3: return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); default: return QString("Error"); } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = 0; switch (i) { case L_pos: b = qBound((float)0, (float)MAX_CHANNEL_L * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)0, - (float)MAX_CHANNEL_AB * values[i], + (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET, (float)MAX_CHANNEL_AB); break; default: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; nativeArray(pixel)[i] = c; } } }; // Float values are not normalised // XXX: is it really necessary to bind them to these ranges? #include #ifdef HAVE_OPENEXR #include struct KoLabF16Traits : public KoLabTraits { static constexpr float MIN_CHANNEL_L = 0; static constexpr float MAX_CHANNEL_L = 100; static constexpr float MIN_CHANNEL_AB = -128; static constexpr float MAX_CHANNEL_AB = +127; // Lab has some... particulars // For instance, float et al. are NOT normalised inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: b = qBound((float)MIN_CHANNEL_L, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)MIN_CHANNEL_AB, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_AB); break; case 3: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif struct KoLabF32Traits : public KoLabTraits { static constexpr float MIN_CHANNEL_L = 0; static constexpr float MAX_CHANNEL_L = 100; static constexpr float MIN_CHANNEL_AB = -128; static constexpr float MAX_CHANNEL_AB = +127; // Lab has some... particulars inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: b = qBound((float)MIN_CHANNEL_L, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)MIN_CHANNEL_AB, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_AB); break; case 3: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; struct KoLabF64Traits : public KoLabTraits { static constexpr double MIN_CHANNEL_L = 0; static constexpr double MAX_CHANNEL_L = 100; static constexpr double MIN_CHANNEL_AB = -128; static constexpr double MAX_CHANNEL_AB = +127; // Lab has some... particulars inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: b = qBound((float)MIN_CHANNEL_L, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)MIN_CHANNEL_AB, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_AB); break; case 3: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 72c7757b8e..70f4945b25 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1652 +1,1652 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia 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; 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 #include #include #include #include #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include "KisSwatch.h" #include "KoColorSet.h" #include "KoColorSet_p.h" namespace { /** * readAllLinesSafe() reads all the lines in the byte array * using the automated UTF8 and CR/LF transformations. That * might be necessary for opening GPL palettes created on Linux * in Windows environment. */ QStringList readAllLinesSafe(QByteArray *data) { QStringList lines; QBuffer buffer(data); buffer.open(QBuffer::ReadOnly); QTextStream stream(&buffer); QString line; while (stream.readLineInto(&line)) { lines << line; } return lines; } } const QString KoColorSet::GLOBAL_GROUP_NAME = QString(); const QString KoColorSet::KPL_VERSION_ATTR = "version"; const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows"; const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns"; const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name"; const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment"; const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename"; const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly"; const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId"; const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId"; const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row"; const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column"; const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id"; const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot"; const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth"; const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile"; const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position"; const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry"; const QString KoColorSet::KPL_GROUP_TAG = "Group"; const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet"; const int MAXIMUM_ALLOWED_COLUMNS = 4096; KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private(this)) { if (!filename.isEmpty()) { QFileInfo f(filename); setIsEditable(f.isWritable()); } } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(rhs) , d(new Private(this)) { d->paletteType = rhs.d->paletteType; d->data = rhs.d->data; d->comment = rhs.d->comment; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; d->isGlobal = rhs.d->isGlobal; d->isEditable = rhs.d->isEditable; } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); if (!QFileInfo(filename()).isWritable()) { setIsEditable(false); } return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return d->init(); } bool KoColorSet::save() { if (d->isGlobal) { // save to resource dir QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } else { return true; // palette is not global, but still indicate that it's saved } } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = d->saveGpl(dev); break; default: res = d->saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } QByteArray KoColorSet::toByteArray() const { QBuffer s; s.open(QIODevice::WriteOnly); if (!saveToDevice(&s)) { warnPigment << "saving palette failed:" << name(); return QByteArray(); } s.close(); s.open(QIODevice::ReadOnly); QByteArray res = s.readAll(); s.close(); return res; } bool KoColorSet::fromByteArray(QByteArray &data) { QBuffer buf(&data); buf.open(QIODevice::ReadOnly); return loadFromDevice(&buf); } KoColorSet::PaletteType KoColorSet::paletteType() const { return d->paletteType; } void KoColorSet::setPaletteType(PaletteType paletteType) { d->paletteType = paletteType; QString suffix; switch(d->paletteType) { case GPL: suffix = ".gpl"; break; case ACT: suffix = ".act"; break; case RIFF_PAL: case PSP_PAL: suffix = ".pal"; break; case ACO: suffix = ".aco"; break; case XML: suffix = ".xml"; break; case KPL: suffix = ".kpl"; break; case SBZ: suffix = ".sbz"; break; default: suffix = defaultFileExtension(); } QStringList fileName = filename().split("."); fileName.last() = suffix.replace(".", ""); setFilename(fileName.join(".")); } quint32 KoColorSet::colorCount() const { - int colorCount = d->groups[GLOBAL_GROUP_NAME].colorCount(); + int colorCount = 0; for (KisSwatchGroup &g : d->groups.values()) { colorCount += g.colorCount(); } return colorCount; } void KoColorSet::add(const KisSwatch &c, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.addEntry(c); } void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.setEntry(e, x, y); } void KoColorSet::clear() { d->groups.clear(); d->groupNames.clear(); d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup(); d->groupNames.append(GLOBAL_GROUP_NAME); } KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const { int yInGroup = y; QString nameGroupFoundIn; for (const QString &groupName : d->groupNames) { if (yInGroup < d->groups[groupName].rowCount()) { nameGroupFoundIn = groupName; break; } else { yInGroup -= d->groups[groupName].rowCount(); } } const KisSwatchGroup &groupFoundIn = nameGroupFoundIn == GLOBAL_GROUP_NAME ? d->global() : d->groups[nameGroupFoundIn]; Q_ASSERT(groupFoundIn.checkEntry(x, yInGroup)); return groupFoundIn.getEntry(x, yInGroup); } KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName) { KisSwatch e; const KisSwatchGroup &sourceGroup = groupName == QString() ? d->global() : d->groups[groupName]; if (sourceGroup.checkEntry(x, y)) { e = sourceGroup.getEntry(x, y); } return e; } QStringList KoColorSet::getGroupNames() { if (d->groupNames.size() != d->groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName) { if (!d->groups.contains(oldGroupName)) { return false; } if (oldGroupName == newGroupName) { return true; } d->groups[newGroupName] = d->groups[oldGroupName]; d->groups.remove(oldGroupName); d->groups[newGroupName].setName(newGroupName); //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } void KoColorSet::setColumnCount(int columns) { d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns); for (KisSwatchGroup &g : d->groups.values()) { g.setColumnCount(columns); } } int KoColorSet::columnCount() const { return d->groups[GLOBAL_GROUP_NAME].columnCount(); } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName] = KisSwatchGroup(); d->groups[groupName].setName(groupName); return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) { d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.indexOf(groupNameInsertBefore); d->groupNames.insert(index, groupName); } return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (groupName == GLOBAL_GROUP_NAME) { return false; } if (keepColors) { // put all colors directly below global int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount(); for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) { d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch, info.column, info.row + startingRow); } } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } int KoColorSet::rowCount() const { int res = 0; for (const QString &name : d->groupNames) { res += d->groups[name].rowCount(); } return res; } KisSwatchGroup *KoColorSet::getGroup(const QString &name) { if (!d->groups.contains(name)) { return 0; } return &(d->groups[name]); } KisSwatchGroup *KoColorSet::getGlobalGroup() { return getGroup(GLOBAL_GROUP_NAME); } bool KoColorSet::isGlobal() const { return d->isGlobal; } void KoColorSet::setIsGlobal(bool isGlobal) { d->isGlobal = isGlobal; } bool KoColorSet::isEditable() const { return d->isEditable; } void KoColorSet::setIsEditable(bool isEditable) { d->isEditable = isEditable; } KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace) { KisSwatchGroup::SwatchInfo res; quint8 highestPercentage = 0; quint8 testPercentage = 0; for (const QString &groupName : getGroupNames()) { KisSwatchGroup *group = getGroup(groupName); for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) { KoColor color = currInfo.swatch.color(); if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) { color.convertTo(compare.colorSpace()); } else if (compare.colorSpace() != color.colorSpace()) { compare.convertTo(color.colorSpace()); } testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data())); if (testPercentage > highestPercentage) { highestPercentage = testPercentage; res = currInfo; } } } return res; } /********************************KoColorSet::Private**************************/ KoColorSet::Private::Private(KoColorSet *a_colorSet) : colorSet(a_colorSet) , isGlobal(true) , isEditable(false) { groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); } KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KisSwatch colorEntry; // It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorName = colorProperties.value("NAME"); colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString()); // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8()); QStringRef colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.data()[0] = r; currentColor.data()[1] = g; currentColor.data()[2] = b; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); QStringRef colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.data()[0] = c; currentColor.data()[1] = m; currentColor.data()[2] = y; currentColor.data()[3] = k; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + colorEntry.name()); } } bool KoColorSet::Private::loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } quint16 KoColorSet::Private::readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::Private::init() { // just in case this is a reload (eg by KoEditColorSetDialog), groupNames.clear(); groups.clear(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); if (colorSet->filename().isNull()) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set"; return false; } if (data.isNull()) { QFile file(colorSet->filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); data = file.readAll(); file.close(); } bool res = false; paletteType = detectFormat(colorSet->filename(), data); switch(paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } colorSet->setValid(res); QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); for (const KisSwatchGroup::SwatchInfo &info : global().infoList()) { QColor c = info.swatch.color().toQColor(); gc.fillRect(info.column * 4, info.row * 4, 4, 4, c); } colorSet->setImage(img); colorSet->setValid(res); data.clear(); return res; } bool KoColorSet::Private::saveGpl(QIODevice *dev) const { Q_ASSERT(dev->isOpen()); Q_ASSERT(dev->isWritable()); QTextStream stream(dev); stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n"; /* * Qt doesn't provide an interface to get a const reference to a QHash, that is * the underlying data structure of groups. Therefore, directly use * groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const */ for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) { for (int x = 0; x < colorSet->columnCount(); x++) { if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) { continue; } const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y); QColor c = entry.color().toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; if (entry.name().isEmpty()) stream << "Untitled\n"; else stream << entry.name() << "\n"; } } return true; } bool KoColorSet::Private::loadGpl() { if (data.isEmpty() || data.isNull() || data.length() < 50) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } quint32 index = 0; QStringList lines = readAllLinesSafe(&data); if (lines.size() < 3) { warnPigment << "Not enough lines in palette file: " << colorSet->filename(); return false; } QString columnsText; qint32 r, g, b; KisSwatch e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1())); index = 2; // Read columns int columns = 0; if (lines[index].toLower().contains("columns")) { columnsText = lines[index].split(":")[1].trimmed(); columns = columnsText.toInt(); if (columns > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead"; global().setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { global().setColumnCount(columns); } index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); for (int i = 0; i != 3; i++) { a.pop_front(); } QString name = a.join(" "); e.setName(name.isEmpty() ? i18n("Untitled") : name); global().addEntry(e); } } int rowCount = global().colorCount()/ global().columnCount(); if (global().colorCount() % global().columnCount()>0) { rowCount ++; } global().setRowCount(rowCount); return true; } bool KoColorSet::Private::loadAct() { QFileInfo info(colorSet->filename()); colorSet->setName(info.baseName()); KisSwatch e; for (int i = 0; i < data.size(); i += 3) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); global().addEntry(e); } return true; } bool KoColorSet::Private::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(colorSet->filename()); colorSet->setName(info.baseName()); KisSwatch e; RiffHeader header; memcpy(&header, data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < data.size()); i += 4) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadPsp() { QFileInfo info(colorSet->filename()); colorSet->setName(info.baseName()); KisSwatch e; qint32 r, g, b; QStringList l = readAllLinesSafe(&data); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); QString name = a.join(" "); e.setName(name.isEmpty() ? i18n("Untitled") : name); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadKpl() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) { return false; } if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG); while (!c.isNull()) { QString name = c.attribute(KPL_PALETTE_NAME_ATTR); QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR); QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR); QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); int desiredColumnCount; QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR)); colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true"); comment = e.attribute(KPL_PALETTE_COMMENT_ATTR); desiredColumnCount = e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt(); if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount << ") in KPL palette file " << colorSet->filename() << " - setting maximum allowed column count instead."; colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { colorSet->setColumnCount(desiredColumnCount); } loadKplGroup(doc, e, colorSet->getGlobalGroup()); QDomElement g = e.firstChildElement(KPL_GROUP_TAG); while (!g.isNull()) { QString groupName = g.attribute(KPL_GROUP_NAME_ATTR); colorSet->addGroup(groupName); loadKplGroup(doc, g, colorSet->getGroup(groupName)); g = g.nextSiblingElement(KPL_GROUP_TAG); } } buf.close(); return true; } bool KoColorSet::Private::loadAco() { QFileInfo info(colorSet->filename()); colorSet->setName(info.baseName()); QBuffer buf(&data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KisSwatch e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 1) { // HSB QColor qc; qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16()); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 2) { // CMYK KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = quint16_MAX - ch1; reinterpret_cast(c.data())[1] = quint16_MAX - ch2; reinterpret_cast(c.data())[2] = quint16_MAX - ch3; reinterpret_cast(c.data())[3] = quint16_MAX - ch4; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 7) { // LAB KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 8) { // GRAY KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = ch1 * (quint16_MAX / 10000); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else { warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.setName(Utf16Codec->toUnicode(ba)); } else { warnPigment << "Version 2 name block is the wrong size" << colorSet->filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } } return true; } bool KoColorSet::Private::loadSbz() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << colorSet->filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; colorSet->setName(colorName); dbgPigment << "Processed name of palette:" << colorSet->name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { KisSwatch currentEntry; // Set if color is spot currentEntry.setSpotColor(colorElement.attribute("usage") == "spot"); // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name currentEntry.setId(colorId.text()); currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text()); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = l; reinterpret_cast(c.data())[1] = a; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; currentEntry.setColor(c); break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = x; reinterpret_cast(c.data())[1] = y; reinterpret_cast(c.data())[2] = z; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor color(colorSpace); reinterpret_cast(color.data())[0] = c; reinterpret_cast(color.data())[1] = m; reinterpret_cast(color.data())[2] = y; reinterpret_cast(color.data())[3] = k; color.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(color); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = g; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentEntry.id(), currentEntry); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { groups[currentGroupName].addEntry(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } bool KoColorSet::Private::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format"; res = loadScribusXmlPalette(colorSet, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << colorSet->filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << colorSet->filename(); return true; } } bool KoColorSet::Private::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) return false; QSet colorSpaces; { QDomDocument doc; QDomElement root = doc.createElement(KPL_PALETTE_TAG); root.setAttribute(KPL_VERSION_ATTR, "1.0"); root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name()); root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment); root.setAttribute(KPL_PALETTE_READONLY_ATTR, (colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true"); root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount()); root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount()); saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces); for (const QString &groupName : groupNames) { if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { continue; } QDomElement gl = doc.createElement(KPL_GROUP_TAG); gl.setAttribute(KPL_GROUP_NAME_ATTR, groupName); root.appendChild(gl); saveKplGroup(doc, gl, colorSet->getGroup(groupName), colorSpaces); } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); for (const KoColorSpace *colorSpace : colorSpaces) { QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = colorSpace->profile()->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG); el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn); el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name()); el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id()); el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } void KoColorSet::Private::saveKplGroup(QDomDocument &doc, QDomElement &groupEle, const KisSwatchGroup *group, QSet &colorSetSet) const { groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount())); for (const SwatchInfoType &info : group->infoList()) { const KoColorProfile *profile = info.swatch.color().colorSpace()->profile(); // Only save non-builtin profiles.= if (!profile->fileName().isEmpty()) { colorSetSet.insert(info.swatch.color().colorSpace()); } QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG); swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name()); swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id()); swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false"); swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id()); info.swatch.color().toXML(doc, swatchEle); QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG); positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row); positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column); swatchEle.appendChild(positionEle); groupEle.appendChild(swatchEle); } } void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroup *group) { Q_UNUSED(doc); if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) { group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt()); } group->setColumnCount(colorSet->columnCount()); for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG); !swatchEle.isNull(); swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) { QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id()); KisSwatch entry; entry.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId)); entry.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR)); entry.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR)); entry.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false); QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG); if (!positionEle.isNull()) { int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt(); int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt(); if (columnNumber < 0 || columnNumber >= colorSet->columnCount() || rowNumber < 0 ) { warnPigment << "Swatch" << entry.name() << "of palette" << colorSet->name() << "has invalid position."; continue; } group->setEntry(entry, columnNumber, rowNumber); } else { group->addEntry(entry); } } if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull() && group->colorCount() > 0 && group->columnCount() > 0 && (group->colorCount() / (group->columnCount()) + 1) < 20) { group->setRowCount((group->colorCount() / group->columnCount()) + 1); } } diff --git a/libs/ui/KisDecorationsManager.cpp b/libs/ui/KisDecorationsManager.cpp index fd6aed8c25..08015a0230 100644 --- a/libs/ui/KisDecorationsManager.cpp +++ b/libs/ui/KisDecorationsManager.cpp @@ -1,128 +1,131 @@ /* * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2014 Sven Langkamp * * 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 "KisDecorationsManager.h" #include "KisViewManager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas2.h" #include #include #include #include +#include KisDecorationsManager::KisDecorationsManager(KisViewManager* view) : QObject(view) , m_imageView(0) { } KisDecorationsManager::~KisDecorationsManager() { } void KisDecorationsManager::setup(KisActionManager * actionManager) { m_toggleAssistant = actionManager->createAction("view_toggle_painting_assistants"); m_togglePreview = actionManager->createAction("view_toggle_assistant_previews"); m_toggleReferenceImages = actionManager->createAction("view_toggle_reference_images"); updateAction(); } void KisDecorationsManager::setView(QPointer imageView) { // set view is called twice when a document is open, so we need to disconnect the original signals // if m_imageView has already been created. This prevents double signal events firing if (m_imageView) { m_toggleAssistant->disconnect(); m_togglePreview->disconnect(); if (assistantsDecoration()) { assistantsDecoration()->disconnect(this); } if (referenceImagesDecoration()) { referenceImagesDecoration()->disconnect(this); } } m_imageView = imageView; if (m_imageView && !referenceImagesDecoration()) { KisReferenceImagesDecoration *deco = new KisReferenceImagesDecoration(m_imageView, imageView->document()); m_imageView->canvasBase()->addDecoration(deco); } if (m_imageView && !assistantsDecoration()) { KisPaintingAssistantsDecoration *deco = new KisPaintingAssistantsDecoration(m_imageView); m_imageView->canvasBase()->addDecoration(deco); } if (m_imageView && assistantsDecoration()) { connect(m_toggleAssistant, SIGNAL(triggered()), assistantsDecoration(), SLOT(toggleAssistantVisible())); connect(m_togglePreview, SIGNAL(triggered()), assistantsDecoration(), SLOT(toggleOutlineVisible())); connect(assistantsDecoration(), SIGNAL(assistantChanged()), SLOT(updateAction())); + connect(m_imageView->document(), &KisDocument::sigAssistantsChanged, + m_imageView->canvasBase(), QOverload<>::of(&KisCanvas2::updateCanvas)); } if (m_imageView && referenceImagesDecoration()) { connect(m_toggleReferenceImages, SIGNAL(triggered(bool)), referenceImagesDecoration(), SLOT(setVisible(bool)), Qt::UniqueConnection); } updateAction(); } void KisDecorationsManager::updateAction() { if (assistantsDecoration()) { bool enabled = !assistantsDecoration()->assistants().isEmpty(); m_toggleAssistant->setChecked(assistantsDecoration()->visible()); m_toggleAssistant->setEnabled(enabled); m_togglePreview->setChecked(assistantsDecoration()->outlineVisibility()); m_togglePreview->setEnabled(enabled); } else { m_toggleAssistant->setEnabled(false); } if (referenceImagesDecoration()) { m_toggleReferenceImages->setEnabled(referenceImagesDecoration()->documentHasReferenceImages()); m_toggleReferenceImages->setChecked(referenceImagesDecoration()->visible()); } } KisPaintingAssistantsDecorationSP KisDecorationsManager::assistantsDecoration() const { if (m_imageView) { return m_imageView->canvasBase()->paintingAssistantsDecoration(); } return 0; } KisReferenceImagesDecorationSP KisDecorationsManager::referenceImagesDecoration() const { if (m_imageView) { return m_imageView->canvasBase()->referenceImagesDecoration(); } return 0; } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 4d519206f4..861ba46574 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2007 +1,2134 @@ /* 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 #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 + copyFromImpl(rhs, q, CONSTRUCT); } ~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; + bool ownsPaletteList = false; 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()))); } } + void copyFrom(const Private &rhs, KisDocument *q); + void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); + + /// clones the palette list oldList + /// the ownership of the returned KoColorSet * belongs to the caller + QList clonePaletteList(const QList &oldList); + class StrippedSafeSavingLocker; }; +void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) +{ + copyFromImpl(rhs, q, KisDocument::REPLACE); +} + +void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) +{ + if (policy == REPLACE) { + delete docInfo; + } + docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); + unit = rhs.unit; + mimeType = rhs.mimeType; + outputMimeType = rhs.outputMimeType; + + if (policy == REPLACE) { + q->setGuidesConfig(rhs.guidesConfig); + q->setMirrorAxisConfig(rhs.mirrorAxisConfig); + q->setModified(rhs.modified); + q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); + q->setGridConfig(rhs.gridConfig); + } else { + // in CONSTRUCT mode, we cannot use the functions of KisDocument + // because KisDocument does not yet have a pointer to us. + guidesConfig = rhs.guidesConfig; + mirrorAxisConfig = rhs.mirrorAxisConfig; + modified = rhs.modified; + assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); + gridConfig = rhs.gridConfig; + } + m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; + m_url = rhs.m_url; + m_file = rhs.m_file; + readwrite = rhs.readwrite; + firstMod = rhs.firstMod; + lastMod = rhs.lastMod; + // XXX: the display properties will be shared between different snapshots + globalAssistantsColor = rhs.globalAssistantsColor; + + if (policy == REPLACE) { + QList newPaletteList = clonePaletteList(rhs.paletteList); + q->setPaletteList(newPaletteList, /* emitSignal = */ true); + // we still do not own palettes if we did not + } else { + paletteList = rhs.paletteList; + } + + batchMode = rhs.batchMode; +} + +QList KisDocument::Private::clonePaletteList(const QList &oldList) +{ + QList newList; + Q_FOREACH (KoColorSet *palette, oldList) { + newList << new KoColorSet(*palette); + } + return newList; +} + 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); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); 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); - d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); - - 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()); - } + copyFromDocumentImpl(rhs, CONSTRUCT); } 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()); } + if (d->ownsPaletteList) { + qDeleteAll(d->paletteList); + } + 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, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); //return ImportExportCodes::NoAccessToWrite; 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); if (job.mimeType.isEmpty()) { KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; slotCompleteSavingDocument(job, error, error.errorMessage()); return false; } const QString actionName = - job.flags & KritaUtils::SaveIsExporting ? - i18n("Exporting Document...") : - i18n("Saving Document..."); + job.flags & KritaUtils::SaveIsExporting ? + i18n("Exporting Document...") : + i18n("Saving Document..."); bool started = - initiateSavingInBackground(actionName, - this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), - job, exportConfiguration); + initiateSavingInBackground(actionName, + this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,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).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { 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); } +KisDocument *KisDocument::lockAndCreateSnapshot() +{ + KisDocument *doc = lockAndCloneForSaving(); + if (doc) { + // clone palette list + doc->d->paletteList = doc->d->clonePaletteList(doc->d->paletteList); + doc->d->ownsPaletteList = true; + } + return doc; +} + +void KisDocument::copyFromDocument(const KisDocument &rhs) +{ + copyFromDocumentImpl(rhs, REPLACE); +} + +void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) +{ + if (policy == REPLACE) { + d->copyFrom(*(rhs.d), this); + + d->undoStack->clear(); + } else { + // in CONSTRUCT mode, d should be already initialized + 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())); + + d->shapeController = new KisShapeController(this, d->nserver); + d->koShapeController = new KoShapeController(0, d->shapeController); + d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); + } + + setObjectName(rhs.objectName()); + + slotConfigChanged(); + + if (rhs.d->image) { + if (policy == REPLACE) { + d->image->barrierLock(/* readOnly = */ false); + rhs.d->image->barrierLock(/* readOnly = */ true); + d->image->copyFromImage(*(rhs.d->image)); + d->image->unlock(); + rhs.d->image->unlock(); + setCurrentImage(d->image, /* forceInitialUpdate = */ true); + } else { + // clone the image with keeping the GUIDs of the layers intact + // NOTE: we expect the image to be locked! + setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); + } + } + + if (rhs.d->preActivatedNode) { + QQueue linearizedNodes; + KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), + [&linearizedNodes](KisNodeSP node) { + linearizedNodes.enqueue(node); + }); + KisLayerUtils::recursiveApplyNodes(d->image->root(), + [&linearizedNodes, &rhs, this](KisNodeSP node) { + KisNodeSP refNode = linearizedNodes.dequeue(); + if (rhs.d->preActivatedNode.data() == refNode.data()) { + d->preActivatedNode = node; + } + }); + } + + KisNodeSP foundNode = KisLayerUtils::recursiveFindNode(image()->rootLayer(), [](KisNodeSP node) -> bool { return dynamic_cast(node.data()); }); + KisReferenceImagesLayer *refLayer = dynamic_cast(foundNode.data()); + setReferenceImagesLayer(refLayer, /* updateImage = */ false); + + if (policy == REPLACE) { + setModified(true); + } +} + bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that noone else uses the document (usually, * it is a temporary docuent created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status.isOk(); } 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; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } 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(KisImportExportErrorCode, QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, 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(KisImportExportErrorCode 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.isOk() ? 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, KisImportExportErrorCode, 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, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { 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); } } KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.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(ImportExportCodes::InternalError, ""); return; } KisImportExportErrorCode status = d->childSavingFuture.result(); const QString errorMessage = status.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() || !fi.isWritable()) { // 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)) { 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); } KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } QString msg = status.errorMessage(); 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); if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { if (!d->undoStack->isClean()) { d->undoStack->clear(); } 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; + if (d->gridConfig != config) { + d->gridConfig = config; + emit sigGridConfigChanged(config); + } } QList &KisDocument::paletteList() { return d->paletteList; } -void KisDocument::setPaletteList(const QList &paletteList) +void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal) { - d->paletteList = paletteList; + if (d->paletteList != paletteList) { + QList oldPaletteList = d->paletteList; + d->paletteList = paletteList; + if (emitSignal) { + emit sigPaletteListChanged(oldPaletteList, 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; + if (d->assistants != value) { + d->assistants = value; + emit sigAssistantsChanged(); + } } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { + if (d->referenceImagesLayer == layer) { + return; + } + 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())); } + emit sigReferenceImagesLayerChanged(layer); } 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(KisImportExportErrorCode status, const QString &errorMessage) { return errorMessage.isEmpty() ? status.errorMessage() : 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/KisDocument.h b/libs/ui/KisDocument.h index 2ccdbfeb64..c10d628805 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,667 +1,692 @@ /* 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. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class KisMirrorAxisConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); /** * Exports he document is a synchronous way. The caller must ensure that the * image is not accessed by any other actors, because the exporting happens * without holding the image lock. */ bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); const KisMirrorAxisConfig& mirrorAxisConfig() const; void setMirrorAxisConfig(const KisMirrorAxisConfig& config); QList &paletteList(); - void setPaletteList(const QList &paletteList); + void setPaletteList(const QList &paletteList, bool emitSignal = false); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void sigReferenceImagesChanged(); void sigMirrorAxisConfigChanged(); + void sigGridConfigChanged(const KisGridConfig &config); + + void sigReferenceImagesLayerChanged(KisSharedPtr layer); + + /** + * Emitted when the palette list has changed. + * The pointers in oldPaletteList are to be deleted by the resource server. + **/ + void sigPaletteListChanged(const QList &oldPaletteList, const QList &newPaletteList); + + void sigAssistantsChanged(); + private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); - -private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); +public: + + KisDocument *lockAndCreateSnapshot(); + + void copyFromDocument(const KisDocument &rhs); + +private: + + enum CopyPolicy { + CONSTRUCT = 0, ///< we are copy-constructing a new KisDocument + REPLACE ///< we are replacing the current KisDocument with another + }; + + void copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy); + QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp index 7f5ee2c4c7..65eaf8dedc 100644 --- a/libs/ui/KisImportExportFilter.cpp +++ b/libs/ui/KisImportExportFilter.cpp @@ -1,289 +1,344 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin 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 "KisImportExportFilter.h" #include +#include #include #include #include "KisImportExportManager.h" #include #include #include #include #include "KoUpdater.h" #include #include "kis_config.h" +#include +#include const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency"; const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID"; const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID"; const QString KisImportExportFilter::sRGBTag = "sRGB"; class Q_DECL_HIDDEN KisImportExportFilter::Private { public: QPointer updater; QByteArray mime; QString filename; QString realFilename; bool batchmode; QMap capabilities; Private() : updater(0), mime("") , batchmode(false) {} ~Private() { qDeleteAll(capabilities); } }; KisImportExportFilter::KisImportExportFilter(QObject *parent) : QObject(parent) , d(new Private) { } KisImportExportFilter::~KisImportExportFilter() { if (d->updater) { d->updater->setProgress(100); } delete d; } QString KisImportExportFilter::filename() const { return d->filename; } QString KisImportExportFilter::realFilename() const { return d->realFilename; } bool KisImportExportFilter::batchMode() const { return d->batchmode; } void KisImportExportFilter::setBatchMode(bool batchmode) { d->batchmode = batchmode; } void KisImportExportFilter::setFilename(const QString &filename) { d->filename = filename; } void KisImportExportFilter::setRealFilename(const QString &filename) { d->realFilename = filename; } void KisImportExportFilter::setMimeType(const QString &mime) { d->mime = mime.toLatin1(); } QByteArray KisImportExportFilter::mimeType() const { return d->mime; } QString KisImportExportFilter::conversionStatusString(ConversionStatus status) { QString msg; switch (status) { case OK: break; case FilterCreationError: msg = i18n("Krita does not support this file format"); break; case CreationError: msg = i18n("Could not create the output document"); break; case FileNotFound: msg = i18n("File not found"); break; case StorageCreationError: msg = i18n("Cannot create storage"); break; case BadMimeType: msg = i18n("Bad MIME type"); break; case WrongFormat: msg = i18n("Format not recognized"); break; case NotImplemented: msg = i18n("Not implemented"); break; case ParsingError: msg = i18n("Parsing error"); break; case InvalidFormat: msg = i18n("Invalid file format"); break; case InternalError: case UsageError: msg = i18n("Internal error"); break; case ProgressCancelled: msg = i18n("Cancelled by user"); break; case BadConversionGraph: msg = i18n("Unknown file type"); break; case UnsupportedVersion: msg = i18n("Unsupported file version"); break; case UserCancelled: // intentionally we do not prompt the error message here break; default: msg = i18n("Unknown error"); break; } return msg; } KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); const QString filterConfig = KisConfig(true).exportConfigurationXML(to); if (cfg && !filterConfig.isEmpty()) { cfg->fromXML(filterConfig, false); } return cfg; } KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } QMap KisImportExportFilter::exportChecks() { qDeleteAll(d->capabilities); initializeCapabilities(); return d->capabilities; } +QString KisImportExportFilter::verify(const QString &fileName) const +{ + QFileInfo fi(fileName); + + if (!fi.exists()) { + return i18n("%1 does not exit after writing. Try saving again under a different name, in another location.", fileName); + } + + if (!fi.isReadable()) { + return i18n("%1 is not readable", fileName); + } + + if (fi.size() < 10) { + return i18n("%1 is smaller than 10 bytes, it must be corrupt. Try saving again under a different name, in another location.", fileName); + } + + QFile f(fileName); + f.open(QFile::ReadOnly); + QByteArray ba = f.read(std::min(f.size(), (qint64)1000)); + bool found = false; + for(int i = 0; i < ba.size(); ++i) { + if (ba.at(i) > 0) { + found = true; + break; + } + } + + if (!found) { + return i18n("%1 has only zero bytes in the first 1000 bytes, it's probably corrupt. Try saving again under a different name, in another location.", fileName); + } + + return QString(); +} + void KisImportExportFilter::setUpdater(QPointer updater) { d->updater = updater; } void KisImportExportFilter::setProgress(int value) { if (d->updater) { d->updater->setValue(value); } } void KisImportExportFilter::initializeCapabilities() { // XXX: Initialize everything to fully supported? } void KisImportExportFilter::addCapability(KisExportCheckBase *capability) { d->capabilities[capability->id()] = capability; } void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level) { Q_ASSERT(level != KisExportCheckBase::SUPPORTED); QString layerMessage; QString imageMessage; QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorModelID, allColorModels) { QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorDepthID, allColorDepths) { KisExportCheckFactory *colorModelCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id()); KisExportCheckFactory *colorModelPerLayerCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id()); if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) { qWarning() << "No factory for" << colorModelID << colorDepthID; continue; } if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) { addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED)); addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED)); } else { if (level == KisExportCheckBase::PARTIALLY) { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will be converted." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be converted or skipped." ,name, colorModelID.name(), colorDepthID.name()); } else { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will not be saved." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be skipped." , name, colorModelID.name(), colorDepthID.name()); } addCapability(colorModelCheckFactory->create(level, imageMessage)); addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage)); } } } } + +QString KisImportExportFilter::verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const +{ + QScopedPointer store(KoStore::createStore(fileName, KoStore::Read, KIS_MIME_TYPE, KoStore::Zip)); + + if (!store || store->bad()) { + return i18n("Could not open the saved file %1. Please try to save again in a different location.", fileName); + } + + Q_FOREACH(const QString &file, filesToCheck) { + if (!store->hasFile(file)) { + return i18n("File %1 is missing in %2 and is broken. Please try to save again in a different location.", file, fileName); + } + } + + return QString(); + +} diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h index 48282d157b..190b803617 100644 --- a/libs/ui/KisImportExportFilter.h +++ b/libs/ui/KisImportExportFilter.h @@ -1,185 +1,190 @@ /* This file is part of the Calligra libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin 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. */ #ifndef KIS_IMPORT_EXPORT_FILTER_H #define KIS_IMPORT_EXPORT_FILTER_H #include "KisImageBuilderResult.h" #include #include #include #include #include #include #include #include #include #include #include #include class KoUpdater; class KisDocument; class KisConfigWidget; #include "kritaui_export.h" #include "KisImportExportErrorCode.h" /** * @brief The base class for import and export filters. * * Derive your filter class from this base class and implement * the @ref convert() method. Don't forget to specify the Q_OBJECT * macro in your class even if you don't use signals or slots. * This is needed as filters are created on the fly. * * @note Take care: The m_chain pointer is invalid while the constructor * runs due to the implementation -- @em don't use it in the constructor. * After the constructor, when running the @ref convert() method it's * guaranteed to be valid, so no need to check against 0. * * @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS * environment variable to any value disables deletion of temporary files while * importing/exporting. This is useful for testing purposes. * * @author Werner Trobin * @todo the class has no constructor and therefore cannot initialize its private class */ class KRITAUI_EXPORT KisImportExportFilter : public QObject { Q_OBJECT public: static const QString ImageContainsTransparencyTag; static const QString ColorModelIDTag; static const QString ColorDepthIDTag; static const QString sRGBTag; public: /** * This enum is used to signal the return state of your filter. * Return OK in @ref convert() in case everything worked as expected. * Feel free to add some more error conditions @em before the last item * if it's needed. */ enum ConversionStatus { OK, UsageError, CreationError, FileNotFound, StorageCreationError, BadMimeType, BadConversionGraph, WrongFormat, NotImplemented, ParsingError, InternalError, UserCancelled, InvalidFormat, FilterCreationError, ProgressCancelled, UnsupportedVersion, JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255 }; ~KisImportExportFilter() override; void setBatchMode(bool batchmode); void setFilename(const QString &filename); void setRealFilename(const QString &filename); void setMimeType(const QString &mime); void setUpdater(QPointer updater); /** * The filter chain calls this method to perform the actual conversion. * The passed mimetypes should be a pair of those you specified in your * .desktop file. * You @em have to implement this method to make the filter work. * * @return The error status, see the @ref #ConversionStatus enum. * KisImportExportFilter::OK means that everything is alright. */ virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0; /** * Get the text version of the status value */ static QString conversionStatusString(ConversionStatus status); /** * @brief defaultConfiguration defines the default settings for the given import export filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief lastSavedConfiguration return the last saved configuration for this filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const; /** * @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter * @param parent the owner of the widget; the caller is responsible for deleting * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * * @return the widget */ virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief generate and return the list of capabilities of this export filter. The list * @return returns the list of capabilities of this export filter */ virtual QMap exportChecks(); /// Override and return false for the filters that use a library that cannot handle file handles, only file names. virtual bool supportsIO() const { return true; } + /// Verify whether the given file is correct and readable + virtual QString verify(const QString &fileName) const; + protected: /** * This is the constructor your filter has to call, obviously. */ KisImportExportFilter(QObject *parent = 0); QString filename() const; QString realFilename() const; bool batchMode() const; QByteArray mimeType() const; void setProgress(int value); virtual void initializeCapabilities(); void addCapability(KisExportCheckBase *capability); void addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY); + QString verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const; + private: KisImportExportFilter(const KisImportExportFilter& rhs); KisImportExportFilter& operator=(const KisImportExportFilter& rhs); class Private; Private *const d; }; #endif diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index 33c5a40ea7..eb552c1d4f 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,687 +1,702 @@ /* * Copyright (C) 2016 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 "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" #include "KisReferenceImagesLayer.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } ConversionResult(KisImportExportErrorCode status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk()); return m_futureStatus; } KisImportExportErrorCode status() const { return m_status; } void setStatus(KisImportExportErrorCode value) { m_status = value; } private: bool m_isAsync = false; QFuture m_futureStatus; KisImportExportErrorCode m_status = ImportExportCodes::InternalError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || !result.status().isOk(), QFuture()); status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::supportedMimeTypes(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } bool KisImportExportManager::batchMode(void) const { return m_document->fileBatchMode(); } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect); } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph" ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK); if (direction == Import) { KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode())); // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra); if (!batchMode() && !askUser) { return KisImportExportErrorCode(ImportExportCodes::Cancelled); } KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode()) .arg(exportConfiguration ? exportConfiguration->toXML() : "none")); if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); // we should explicitly report that the exporting has been initiated result.setStatus(ImportExportCodes::OK); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration && !batchMode() && showWarnings) { KisConfig(false).setExportConfiguration(typeName, exportConfiguration); } } return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) { KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { // prevents the animation renderer from running this code const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = 0; if (QThread::currentThread() == qApp->thread()) { wdg = filter->createConfigurationWidget(0, from, to); + + KisMainWindow *kisMain = KisPart::instance()->currentMainwindow(); + if (wdg && kisMain) { + KisViewManager *manager = kisMain->viewManager(); + wdg->setView(manager); + } } // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

" + i18n("Error: cannot save this image as a %1.", mimeUserDescription) + " Reasons:

" + "

    "; Q_FOREACH(const QString &w, errors) { error += "\n
  • " + w + "
  • "; } error += "
"; QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); return false; } if (!batchMode && (wdg || !warnings.isEmpty())) { KoDialog dlg; dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setWindowTitle(mimeUserDescription); QWidget *page = new QWidget(&dlg); QVBoxLayout *layout = new QVBoxLayout(page); if (showWarnings && !warnings.isEmpty()) { QHBoxLayout *hLayout = new QHBoxLayout(); QLabel *labelWarning = new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hLayout->addWidget(labelWarning); KisPopupButton *bn = new KisPopupButton(0); bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription)); hLayout->addWidget(bn); layout->addLayout(hLayout); QTextBrowser *browser = new QTextBrowser(); browser->setMinimumWidth(bn->width()); bn->setPopupWidget(browser); QString warning = "

" + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { return ImportExportCodes::FileNotExist; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error())); } KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { KisImportExportErrorCode status = doExportImpl(location, filter, exportConfiguration); if (alsoAsKra && status.isOk()) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { status = ImportExportCodes::FileFormatIncorrect; } } return status; } // Temporary workaround until QTBUG-57299 is fixed. #ifndef Q_OS_WIN #define USE_QSAVEFILE #endif KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { #ifdef USE_QSAVEFILE QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { #else QFileInfo fi(location); QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra"); if (filter->supportsIO() && !file.open()) { #endif KisImportExportErrorCannotWrite result(file.error()); #ifdef USE_QSAVEFILE file.cancelWriting(); #endif return result; } KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration); if (filter->supportsIO()) { if (!status.isOk()) { #ifdef USE_QSAVEFILE file.cancelWriting(); #endif } else { #ifdef USE_QSAVEFILE if (!file.commit()) { qWarning() << "Could not commit QSaveFile"; status = KisImportExportErrorCannotWrite(file.error()); } #else file.flush(); file.close(); QFile target(location); if (target.exists()) { // There should already be a .kra~ backup target.remove(); } if (!file.copy(location)) { file.setAutoRemove(false); return KisImportExportErrorCannotWrite(file.error()); } #endif } } + + // Do some minimal verification + QString verificationResult = filter->verify(location); + if (!verificationResult.isEmpty()) { + status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting); + m_document->setErrorMessage(verificationResult); + } + + return status; } #include diff --git a/libs/ui/KisReferenceImagesDecoration.cpp b/libs/ui/KisReferenceImagesDecoration.cpp index b3d7e2ce1c..4977d69335 100644 --- a/libs/ui/KisReferenceImagesDecoration.cpp +++ b/libs/ui/KisReferenceImagesDecoration.cpp @@ -1,173 +1,178 @@ /* * Copyright (C) 2016 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 "KisReferenceImagesDecoration.h" #include "KoShapeManager.h" #include "kis_algebra_2d.h" #include "KisDocument.h" #include "KisReferenceImagesLayer.h" struct KisReferenceImagesDecoration::Private { struct Buffer { /// Top left corner of the buffer relative to the viewport QPointF position; QImage image; QRectF bounds() const { return QRectF(position, image.size()); } }; KisReferenceImagesDecoration *q; KisWeakSharedPtr layer; Buffer buffer; QTransform previousTransform; QSizeF previousViewSize; explicit Private(KisReferenceImagesDecoration *q) : q(q) {} void updateBufferByImageCoordinates(const QRectF &dirtyImageRect) { QRectF dirtyWidgetRect = q->view()->viewConverter()->imageToWidget(dirtyImageRect); updateBuffer(dirtyWidgetRect, dirtyImageRect); } void updateBufferByWidgetCoordinates(const QRectF &dirtyWidgetRect) { QRectF dirtyImageRect = q->view()->viewConverter()->widgetToImage(dirtyWidgetRect); updateBuffer(dirtyWidgetRect, dirtyImageRect); } private: void updateBuffer(QRectF widgetRect, QRectF imageRect) { KisCoordinatesConverter *viewConverter = q->view()->viewConverter(); QTransform transform = viewConverter->imageToWidgetTransform(); if (buffer.image.isNull() || !buffer.bounds().contains(widgetRect)) { const QRectF boundingImageRect = layer->boundingImageRect(); const QRectF boundingWidgetRect = q->view()->viewConverter()->imageToWidget(boundingImageRect); widgetRect = boundingWidgetRect.intersected(q->view()->rect()); if (widgetRect.isNull()) return; buffer.position = widgetRect.topLeft(); buffer.image = QImage(widgetRect.size().toSize(), QImage::Format_ARGB32); buffer.image.fill(Qt::transparent); imageRect = q->view()->viewConverter()->widgetToImage(widgetRect); } QPainter gc(&buffer.image); gc.translate(-buffer.position); gc.setTransform(transform, true); gc.save(); gc.setCompositionMode(QPainter::CompositionMode_Source); gc.fillRect(imageRect, Qt::transparent); gc.restore(); gc.setClipRect(imageRect); layer->paintReferences(gc); } }; KisReferenceImagesDecoration::KisReferenceImagesDecoration(QPointer parent, KisDocument *document) : KisCanvasDecoration("referenceImagesDecoration", parent) , d(new Private(this)) { connect(document->image().data(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), this, SLOT(slotNodeAdded(KisNodeSP))); + connect(document, &KisDocument::sigReferenceImagesLayerChanged, this, &KisReferenceImagesDecoration::slotNodeAdded); auto referenceImageLayer = document->referenceImagesLayer(); if (referenceImageLayer) { setReferenceImageLayer(referenceImageLayer); } } KisReferenceImagesDecoration::~KisReferenceImagesDecoration() {} void KisReferenceImagesDecoration::addReferenceImage(KisReferenceImage *referenceImage) { KisDocument *document = view()->document(); KUndo2Command *cmd = KisReferenceImagesLayer::addReferenceImages(document, {referenceImage}); document->addCommand(cmd); } bool KisReferenceImagesDecoration::documentHasReferenceImages() const { return view()->document()->referenceImagesLayer() != nullptr; } void KisReferenceImagesDecoration::drawDecoration(QPainter &gc, const QRectF &/*updateRect*/, const KisCoordinatesConverter *converter, KisCanvas2 */*canvas*/) { // TODO: can we use partial updates here? KisSharedPtr layer = d->layer.toStrongRef(); if (!layer.isNull()) { QSizeF viewSize = view()->size(); QTransform transform = converter->imageToWidgetTransform(); if (d->previousViewSize != viewSize || !KisAlgebra2D::fuzzyMatrixCompare(transform, d->previousTransform, 1e-4)) { d->previousViewSize = viewSize; d->previousTransform = transform; d->buffer.image = QImage(); d->updateBufferByWidgetCoordinates(QRectF(QPointF(0,0), viewSize)); } if (!d->buffer.image.isNull()) { gc.drawImage(d->buffer.position, d->buffer.image); } } } void KisReferenceImagesDecoration::slotNodeAdded(KisNodeSP node) { auto *referenceImagesLayer = dynamic_cast(node.data()); if (referenceImagesLayer) { setReferenceImageLayer(referenceImagesLayer); } } void KisReferenceImagesDecoration::slotReferenceImagesChanged(const QRectF &dirtyRect) { d->updateBufferByImageCoordinates(dirtyRect); QRectF documentRect = view()->viewConverter()->imageToDocument(dirtyRect); view()->canvasBase()->updateCanvas(documentRect); } void KisReferenceImagesDecoration::setReferenceImageLayer(KisSharedPtr layer) { - d->layer = layer; - connect( - layer.data(), SIGNAL(sigUpdateCanvas(QRectF)), - this, SLOT(slotReferenceImagesChanged(QRectF)) - ); + if (d->layer.data() != layer.data()) { + if (d->layer) { + d->layer->disconnect(this); + } + d->layer = layer; + connect(layer.data(), SIGNAL(sigUpdateCanvas(QRectF)), + this, SLOT(slotReferenceImagesChanged(QRectF))); + slotReferenceImagesChanged(layer->extent()); + } } diff --git a/libs/ui/KisWelcomePageWidget.cpp b/libs/ui/KisWelcomePageWidget.cpp index 8aa3bfb4c1..9bba1b17ce 100644 --- a/libs/ui/KisWelcomePageWidget.cpp +++ b/libs/ui/KisWelcomePageWidget.cpp @@ -1,337 +1,341 @@ /* This file is part of the KDE project * Copyright (C) 2018 Scott Petrovic * * 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 "KisWelcomePageWidget.h" #include #include #include #include #include "kis_action_manager.h" #include "kactioncollection.h" #include "kis_action.h" #include "KConfigGroup" #include "KSharedConfig" #include #include #include "kis_icon_utils.h" #include "krita_utils.h" #include "KoStore.h" #include "kis_config.h" KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent) : QWidget(parent) { setupUi(this); recentDocumentsListView->setDragEnabled(false); recentDocumentsListView->viewport()->setAutoFillBackground(false); recentDocumentsListView->setSpacing(2); // set up URLs that go to web browser - manualLink->setText(QString("
").append(i18n("User Manual")).append("")); manualLink->setTextFormat(Qt::RichText); manualLink->setTextInteractionFlags(Qt::TextBrowserInteraction); manualLink->setOpenExternalLinks(true); - gettingStartedLink->setText(QString("").append(i18n("Getting Started")).append("")); gettingStartedLink->setTextFormat(Qt::RichText); gettingStartedLink->setTextInteractionFlags(Qt::TextBrowserInteraction); gettingStartedLink->setOpenExternalLinks(true); - supportKritaLink->setText(QString("").append(i18n("Support Krita")).append("")); supportKritaLink->setTextFormat(Qt::RichText); supportKritaLink->setTextInteractionFlags(Qt::TextBrowserInteraction); supportKritaLink->setOpenExternalLinks(true); - userCommunityLink->setText(QString("").append(i18n("User Community")).append("")); userCommunityLink->setTextFormat(Qt::RichText); userCommunityLink->setTextInteractionFlags(Qt::TextBrowserInteraction); userCommunityLink->setOpenExternalLinks(true); - kritaWebsiteLink->setText(QString("").append(i18n("Krita Website")).append("")); + kritaWebsiteLink->setTextFormat(Qt::RichText); kritaWebsiteLink->setTextInteractionFlags(Qt::TextBrowserInteraction); kritaWebsiteLink->setOpenExternalLinks(true); - sourceCodeLink->setText(QString("").append(i18n("Source Code")).append("")); + sourceCodeLink->setTextFormat(Qt::RichText); sourceCodeLink->setTextInteractionFlags(Qt::TextBrowserInteraction); sourceCodeLink->setOpenExternalLinks(true); - poweredByKDELink->setText(QString("").append(i18n("Powered by KDE")).append("")); poweredByKDELink->setTextFormat(Qt::RichText); poweredByKDELink->setTextInteractionFlags(Qt::TextBrowserInteraction); poweredByKDELink->setOpenExternalLinks(true); kdeIcon->setIconSize(QSize(20, 20)); kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20)); connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool))); // configure the News area KisConfig cfg(true); bool m_getNews = cfg.readEntry("FetchNews", false); chkShowNews->setChecked(m_getNews); setAcceptDrops(true); } KisWelcomePageWidget::~KisWelcomePageWidget() { } void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin) { if (mainWin) { m_mainWindow = mainWin; // set the shortcut links from actions (only if a shortcut exists) if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") { newFileLinkShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() + QString(")")); } if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") { openFileShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() + QString(")")); } connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex))); // we need the view manager to actually call actions, so don't create the connections // until after the view manager is set connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked())); connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked())); connect(clearRecentFilesLink, SIGNAL(clicked(bool)), this, SLOT(slotClearRecentFiles())); slotUpdateThemeColors(); } } void KisWelcomePageWidget::showDropAreaIndicator(bool show) { if (!show) { QString dropFrameStyle = "QFrame#dropAreaIndicator { border: 0px }"; dropFrameBorder->setStyleSheet(dropFrameStyle); } else { QColor textColor = qApp->palette().color(QPalette::Text); QColor backgroundColor = qApp->palette().color(QPalette::Background); QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); // QColor.name() turns it into a hex/web format QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ; dropFrameBorder->setStyleSheet(dropFrameStyle); } } void KisWelcomePageWidget::slotUpdateThemeColors() { QColor textColor = qApp->palette().color(QPalette::Text); QColor backgroundColor = qApp->palette().color(QPalette::Background); // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); QString blendedStyle = QString("color: ").append(blendedColor.name()); // what labels to change the color... startTitleLabel->setStyleSheet(blendedStyle); recentDocumentsLabel->setStyleSheet(blendedStyle); helpTitleLabel->setStyleSheet(blendedStyle); - manualLink->setStyleSheet(blendedStyle); - gettingStartedLink->setStyleSheet(blendedStyle); - supportKritaLink->setStyleSheet(blendedStyle); - userCommunityLink->setStyleSheet(blendedStyle); - kritaWebsiteLink->setStyleSheet(blendedStyle); - sourceCodeLink->setStyleSheet(blendedStyle); newFileLinkShortcut->setStyleSheet(blendedStyle); openFileShortcut->setStyleSheet(blendedStyle); clearRecentFilesLink->setStyleSheet(blendedStyle); - poweredByKDELink->setStyleSheet(blendedStyle); recentDocumentsListView->setStyleSheet(blendedStyle); newFileLink->setStyleSheet(blendedStyle); openFileLink->setStyleSheet(blendedStyle); // giving the drag area messaging a dotted border QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";"); dragImageHereLabel->setStyleSheet(dottedBorderStyle); // make drop area QFrame have a dotted line dropFrameBorder->setObjectName("dropAreaIndicator"); QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}"); dropFrameBorder->setStyleSheet(dropFrameStyle); // only show drop area when we have a document over the empty area showDropAreaIndicator(false); // add icons for new and open settings to make them stand out a bit more openFileLink->setIconSize(QSize(30, 30)); newFileLink->setIconSize(QSize(30, 30)); openFileLink->setIcon(KisIconUtils::loadIcon("document-open")); newFileLink->setIcon(KisIconUtils::loadIcon("document-new")); + + + kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20)); + + // HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change + userCommunityLink->setText(QString("").append(i18n("User Community")).append("")); + gettingStartedLink->setText(QString("").append(i18n("Getting Started")).append("")); + manualLink->setText(QString("").append(i18n("User Manual")).append("")); + supportKritaLink->setText(QString("").append(i18n("Support Krita")).append("")); + kritaWebsiteLink->setText(QString("").append(i18n("Krita Website")).append("")); + sourceCodeLink->setText(QString("").append(i18n("Source Code")).append("")); + poweredByKDELink->setText(QString("").append(i18n("Powered by KDE")).append("")); + + // re-populate recent files since they might have themed icons + populateRecentDocuments(); + } void KisWelcomePageWidget::populateRecentDocuments() { m_recentFilesModel.clear(); // clear existing data before it gets re-populated // grab recent files data int numRecentFiles = m_mainWindow->recentFilesUrls().length() > 5 ? 5 : m_mainWindow->recentFilesUrls().length(); // grab at most 5 for (int i = 0; i < numRecentFiles; i++ ) { QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column recentItem->setIcon(KisIconUtils::loadIcon("document-export")); QString recentFileUrlPath = m_mainWindow->recentFilesUrls().at(i).toLocalFile(); QString fileName = recentFileUrlPath.split("/").last(); if (m_thumbnailMap.contains(recentFileUrlPath)) { recentItem->setIcon(m_thumbnailMap[recentFileUrlPath]); } else { if (QFileInfo(recentFileUrlPath).exists()) { if (recentFileUrlPath.toLower().endsWith("ora") || recentFileUrlPath.toLower().endsWith("kra")) { QScopedPointer store(KoStore::createStore(recentFileUrlPath, KoStore::Read)); if (store) { QString thumbnailpath; if (store->hasFile(QString("Thumbnails/thumbnail.png"))){ thumbnailpath = QString("Thumbnails/thumbnail.png"); } else if (store->hasFile(QString("preview.png"))) { thumbnailpath = QString("preview.png"); } if (!thumbnailpath.isEmpty()) { if (store->open(thumbnailpath)) { QByteArray bytes = store->read(store->size()); store->close(); QImage img; img.loadFromData(bytes); img.setDevicePixelRatio(devicePixelRatioF()); recentItem->setIcon(QIcon(QPixmap::fromImage(img))); } } } } else { QImage img; img.setDevicePixelRatio(devicePixelRatioF()); img.load(recentFileUrlPath); if (!img.isNull()) { recentItem->setIcon(QIcon(QPixmap::fromImage(img.scaledToWidth(48)))); } } m_thumbnailMap[recentFileUrlPath] = recentItem->icon(); } } // set the recent object with the data recentItem->setText(fileName); // what to display for the item recentItem->setToolTip(recentFileUrlPath); m_recentFilesModel.appendRow(recentItem); } // hide clear and Recent files title if there are none bool hasRecentFiles = m_mainWindow->recentFilesUrls().length() > 0; recentDocumentsLabel->setVisible(hasRecentFiles); clearRecentFilesLink->setVisible(hasRecentFiles); recentDocumentsListView->setIconSize(QSize(48, 48)); recentDocumentsListView->setModel(&m_recentFilesModel); } void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event) { //qDebug() << "dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); showDropAreaIndicator(true); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisWelcomePageWidget::dropEvent(QDropEvent *event) { //qDebug() << "KisWelcomePageWidget::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = m_mainWindow->installBundle(url.toLocalFile()); if (!r) { qWarning() << "Could not install bundle" << url.toLocalFile(); } } else { m_mainWindow->openDocument(url, KisMainWindow::None); } } } } void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event) { //qDebug() << "dragMoveEvent"; m_mainWindow->dragMoveEvent(event); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/) { //qDebug() << "dragLeaveEvent"; showDropAreaIndicator(false); m_mainWindow->dragLeave(); } void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index) { QString fileUrl = index.data(Qt::ToolTipRole).toString(); m_mainWindow->openDocument(QUrl::fromLocalFile(fileUrl), KisMainWindow::None ); } void KisWelcomePageWidget::slotNewFileClicked() { m_mainWindow->slotFileNew(); } void KisWelcomePageWidget::slotOpenFileClicked() { m_mainWindow->slotFileOpen(); } void KisWelcomePageWidget::slotClearRecentFiles() { m_mainWindow->clearRecentFiles(); populateRecentDocuments(); } diff --git a/libs/ui/canvas/kis_grid_manager.cpp b/libs/ui/canvas/kis_grid_manager.cpp index 2efdce6d07..1e4132a644 100644 --- a/libs/ui/canvas/kis_grid_manager.cpp +++ b/libs/ui/canvas/kis_grid_manager.cpp @@ -1,132 +1,152 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 "kis_grid_manager.h" #include #include #include #include #include #include "kis_canvas2.h" #include "kis_coordinates_converter.h" #include "kis_config.h" #include "kis_grid_decoration.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisDocument.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_signals_blocker.h" +#include +struct KisGridManager::Private +{ + KisAction *toggleGrid; + KisAction* toggleSnapToGrid; + + QPointer imageView; + KisGridDecoration* gridDecoration; + + bool blockModifiedSignal; + KisSignalAutoConnectionsStore connections; +}; KisGridManager::KisGridManager(KisViewManager * parent) : QObject(parent) + , m_d(new Private) { } KisGridManager::~KisGridManager() { } void KisGridManager::setGridConfig(const KisGridConfig &config) { setGridConfigImpl(config, true); } void KisGridManager::setGridConfigImpl(const KisGridConfig &config, bool /*emitModified*/) { - if (!m_imageView) return; + if (!m_d->imageView) return; config.saveStaticData(); - m_imageView->document()->setGridConfig(config); + m_d->imageView->document()->setGridConfig(config); - m_gridDecoration->setGridConfig(config); - m_gridDecoration->setVisible(config.showGrid()); + m_d->gridDecoration->setGridConfig(config); + m_d->gridDecoration->setVisible(config.showGrid()); - m_toggleGrid->setChecked(config.showGrid()); - m_toggleSnapToGrid->setChecked(config.snapToGrid()); + m_d->toggleGrid->setChecked(config.showGrid()); + m_d->toggleSnapToGrid->setChecked(config.snapToGrid()); } void KisGridManager::setup(KisActionManager* actionManager) { - m_toggleGrid = actionManager->createAction("view_grid"); - connect(m_toggleGrid, SIGNAL(toggled(bool)), this, SLOT(slotChangeGridVisibilityTriggered(bool))); + m_d->toggleGrid = actionManager->createAction("view_grid"); + connect(m_d->toggleGrid, SIGNAL(toggled(bool)), this, SLOT(slotChangeGridVisibilityTriggered(bool))); - m_toggleSnapToGrid = actionManager->createAction("view_snap_to_grid"); - connect(m_toggleSnapToGrid, SIGNAL(toggled(bool)), this, SLOT(slotSnapToGridTriggered(bool))); + m_d->toggleSnapToGrid = actionManager->createAction("view_snap_to_grid"); + connect(m_d->toggleSnapToGrid, SIGNAL(toggled(bool)), this, SLOT(slotSnapToGridTriggered(bool))); } void KisGridManager::updateGUI() { } void KisGridManager::setView(QPointer imageView) { - if (m_imageView) { - m_gridDecoration = 0; + if (m_d->imageView) { + m_d->connections.clear(); + m_d->gridDecoration = 0; } - m_imageView = imageView; + m_d->imageView = imageView; if (imageView) { - m_gridDecoration = qobject_cast(imageView->canvasBase()->decoration("grid").data()); - if (!m_gridDecoration) { - m_gridDecoration = new KisGridDecoration(imageView); - imageView->canvasBase()->addDecoration(m_gridDecoration); + if (!imageView->document()) { + return; + } + + m_d->gridDecoration = qobject_cast(imageView->canvasBase()->decoration("grid").data()); + if (!m_d->gridDecoration) { + m_d->gridDecoration = new KisGridDecoration(imageView); + imageView->canvasBase()->addDecoration(m_d->gridDecoration); } + m_d->connections.addConnection(imageView->document(), SIGNAL(sigGridConfigChanged(KisGridConfig)), + this, SIGNAL(sigRequestUpdateGridConfig(KisGridConfig))); KisGridConfig config = imageView->document()->gridConfig(); setGridConfigImpl(config, false); - KisSignalsBlocker blocker(m_toggleGrid, m_toggleSnapToGrid); + KisSignalsBlocker blocker(m_d->toggleGrid, m_d->toggleSnapToGrid); Q_UNUSED(blocker); - m_toggleGrid->setChecked(config.showGrid()); - m_toggleSnapToGrid->setChecked(config.snapToGrid()); + m_d->toggleGrid->setChecked(config.showGrid()); + m_d->toggleSnapToGrid->setChecked(config.snapToGrid()); } } void KisGridManager::slotChangeGridVisibilityTriggered(bool value) { - if (!m_imageView) return; + if (!m_d->imageView) return; - KisGridConfig config = m_imageView->document()->gridConfig(); + KisGridConfig config = m_d->imageView->document()->gridConfig(); config.setShowGrid(value); setGridConfig(config); emit sigRequestUpdateGridConfig(config); } void KisGridManager::slotSnapToGridTriggered(bool value) { - if (!m_imageView) return; + if (!m_d->imageView) return; - KisGridConfig config = m_imageView->document()->gridConfig(); + KisGridConfig config = m_d->imageView->document()->gridConfig(); config.setSnapToGrid(value); setGridConfig(config); emit sigRequestUpdateGridConfig(config); } diff --git a/libs/ui/canvas/kis_grid_manager.h b/libs/ui/canvas/kis_grid_manager.h index 51ff7a45d8..84650530f4 100644 --- a/libs/ui/canvas/kis_grid_manager.h +++ b/libs/ui/canvas/kis_grid_manager.h @@ -1,76 +1,72 @@ /* * This file is part of Krita * * Copyright (c) 2006 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. */ #ifndef KIS_GRID_MANAGER_H #define KIS_GRID_MANAGER_H #include #include "kis_types.h" #include #include "kis_action_manager.h" #include "kis_action.h" class KisGridDecoration; class KisViewManager; class KisGridConfig; class KRITAUI_EXPORT KisGridManager : public QObject { Q_OBJECT public: KisGridManager(KisViewManager * parent); ~KisGridManager() override; public: void setup(KisActionManager * actionManager); void setView(QPointerimageView); void setGridConfig(const KisGridConfig &config); Q_SIGNALS: void sigRequestUpdateGridConfig(const KisGridConfig &config); public Q_SLOTS: void updateGUI(); private Q_SLOTS: void slotChangeGridVisibilityTriggered(bool value); void slotSnapToGridTriggered(bool value); private: void setGridConfigImpl(const KisGridConfig &config, bool emitModified); private: void setFastConfig(int size); - KisAction *m_toggleGrid; - KisAction* m_toggleSnapToGrid; - - QPointer m_imageView; - KisGridDecoration* m_gridDecoration; - - bool m_blockModifiedSignal; +private: + struct Private; + QScopedPointer m_d; }; #endif diff --git a/libs/ui/canvas/kis_mirror_axis.cpp b/libs/ui/canvas/kis_mirror_axis.cpp index c226b64205..71d5c4935e 100644 --- a/libs/ui/canvas/kis_mirror_axis.cpp +++ b/libs/ui/canvas/kis_mirror_axis.cpp @@ -1,466 +1,469 @@ /* * Copyright (c) 2014 Arjen Hiemstra * * 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_mirror_axis.h" #include "KoConfig.h" #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisView.h" #include "kis_image.h" #include "canvas/kis_canvas_controller.h" #include "input/kis_input_manager.h" #include "kis_algebra_2d.h" #include #include #include class KisMirrorAxis::Private { public: Private(KisMirrorAxis* qq) : q(qq) , resourceProvider(0) , xActive(false) , yActive(false) , sideMargin(10.f) , minHandlePosition(10.f + 32.f) , horizontalContainsCursor(false) , verticalContainsCursor(false) , horizontalAxis(QLineF()) , verticalAxis(QLineF()) , config(KisMirrorAxisConfig()) { } void setAxisPosition(float x, float y); void recomputeVisibleAxes(QRect viewport); KisMirrorAxis* q; KisCanvasResourceProvider* resourceProvider; KisImageWSP image; QPixmap horizontalHandleIcon; QPixmap verticalHandleIcon; QPixmap horizontalIcon; QPixmap verticalIcon; QRectF horizontalHandle; QRectF verticalHandle; bool xActive; bool yActive; float sideMargin; float minHandlePosition; bool horizontalContainsCursor; bool verticalContainsCursor; QLineF horizontalAxis; QLineF verticalAxis; KisMirrorAxisConfig config; }; KisMirrorAxis::KisMirrorAxis(KisCanvasResourceProvider* provider, QPointerparent) : KisCanvasDecoration("mirror_axis", parent) , d(new Private(this)) { d->resourceProvider = provider; connect(d->resourceProvider, SIGNAL(mirrorModeChanged()), SLOT(mirrorModeChanged())); connect(d->resourceProvider, SIGNAL(moveMirrorVerticalCenter()), SLOT(moveVerticalAxisToCenter())); connect(d->resourceProvider, SIGNAL(moveMirrorHorizontalCenter()), SLOT(moveHorizontalAxisToCenter())); d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal()); d->config.setMirrorVertical(d->resourceProvider->mirrorVertical()); d->horizontalIcon = KisIconUtils::loadIcon("mirrorAxis-HorizontalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->verticalIcon = KisIconUtils::loadIcon("mirrorAxis-VerticalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical()); d->image = parent->canvasBase()->image(); } KisMirrorAxis::~KisMirrorAxis() { } float KisMirrorAxis::handleSize() const { return d->config.handleSize(); } void KisMirrorAxis::setHandleSize(float newSize) { if(d->config.handleSize() != newSize) { d->config.setHandleSize(newSize); d->horizontalIcon = KisIconUtils::loadIcon("symmetry-horizontal").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->verticalIcon = KisIconUtils::loadIcon("symmetry-vertical").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On); d->minHandlePosition = d->sideMargin + newSize; emit handleSizeChanged(); emit sigConfigChanged(); } } void KisMirrorAxis::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas) { Q_UNUSED(updateArea); Q_UNUSED(converter); Q_UNUSED(canvas); if (!view()->isCurrent()) { return; } gc.save(); gc.setPen(QPen(QColor(0, 0, 0, 128), 1)); gc.setBrush(Qt::white); gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); QOpenGLContext *ctx = QOpenGLContext::currentContext(); - bool hasMultisample = ((gc.paintEngine()->type() == QPaintEngine::OpenGL2) && - (ctx->hasExtension("GL_ARB_multisample"))); - - // QPainter cannot anti-alias the edges of circles etc. when using OpenGL - // So instead, use native OpenGL anti-aliasing when available. - if (hasMultisample) { - gc.beginNativePainting(); - ctx->functions()->glEnable(GL_MULTISAMPLE); - gc.endNativePainting(); + bool hasMultisample = false; + if (ctx) { + hasMultisample = ((gc.paintEngine()->type() == QPaintEngine::OpenGL2) && + (ctx->hasExtension("GL_ARB_multisample"))); + + // QPainter cannot anti-alias the edges of circles etc. when using OpenGL + // So instead, use native OpenGL anti-aliasing when available. + if (hasMultisample) { + gc.beginNativePainting(); + ctx->functions()->glEnable(GL_MULTISAMPLE); + gc.endNativePainting(); + } } float halfHandleSize = d->config.handleSize() / 2; d->recomputeVisibleAxes(gc.viewport()); if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) { if (!d->horizontalAxis.isNull()) { - // QPointF horizontalIndicatorCenter = d->horizontalAxis.unitVector().pointAt(15); - // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize()); + // QPointF horizontalIndicatorCenter = d->horizontalAxis.unitVector().pointAt(15); + // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize()); float horizontalHandlePosition = qBound(d->minHandlePosition, d->config.horizontalHandlePosition(), d->horizontalAxis.length() - d->minHandlePosition); QPointF horizontalHandleCenter = d->horizontalAxis.unitVector().pointAt(horizontalHandlePosition); d->horizontalHandle = QRectF(horizontalHandleCenter.x() - halfHandleSize, horizontalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize()); gc.setPen(QPen(QColor(0, 0, 0, 64), 2, Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin)); gc.drawLine(d->horizontalAxis); - // gc.drawEllipse(horizontalIndicator); - // gc.drawPixmap(horizontalIndicator.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon); + // gc.drawEllipse(horizontalIndicator); + // gc.drawPixmap(horizontalIndicator.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon); // don't draw the handles if we are locking the axis for movement if (!d->config.lockHorizontal()) { gc.setPen(QPen(QColor(0, 0, 0, 128), 2)); gc.drawEllipse(d->horizontalHandle); gc.drawPixmap(d->horizontalHandle.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon); } } else { d->horizontalHandle = QRectF(); } } if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) { if (!d->verticalAxis.isNull()) { gc.setPen(QPen(QColor(0, 0, 0, 64), 2, Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin)); gc.drawLine(d->verticalAxis); - // QPointF verticalIndicatorCenter = d->verticalAxis.unitVector().pointAt(15); - // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize()); + // QPointF verticalIndicatorCenter = d->verticalAxis.unitVector().pointAt(15); + // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize()); float verticalHandlePosition = qBound(d->minHandlePosition, d->config.verticalHandlePosition(), d->verticalAxis.length() - d->minHandlePosition); QPointF verticalHandleCenter = d->verticalAxis.unitVector().pointAt(verticalHandlePosition); d->verticalHandle = QRectF(verticalHandleCenter.x() - halfHandleSize, verticalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize()); // don't draw the handles if we are locking the axis for movement if (!d->config.lockVertical()) { gc.setPen(QPen(QColor(0, 0, 0, 128), 2)); gc.drawEllipse(d->verticalHandle); gc.drawPixmap(d->verticalHandle.adjusted(5, 5, -5, -5).toRect(), d->verticalIcon); } } else { d->verticalHandle = QRectF(); } } if (hasMultisample) { gc.beginNativePainting(); ctx->functions()->glDisable(GL_MULTISAMPLE); gc.endNativePainting(); } gc.restore(); } bool KisMirrorAxis::eventFilter(QObject* target, QEvent* event) { if (!visible()) return false; QObject *expectedCanvasWidget = view() ? - view()->canvasBase()->canvasWidget() : 0; + view()->canvasBase()->canvasWidget() : 0; if (!expectedCanvasWidget || target != expectedCanvasWidget) return false; if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TabletPress) { QMouseEvent *me = dynamic_cast(event); QTabletEvent *te = dynamic_cast(event); QPoint pos = me ? me->pos() : (te ? te->pos() : QPoint(77,77)); if(d->config.mirrorHorizontal() && d->horizontalHandle.contains(pos) && !d->config.lockHorizontal() && !d->config.hideHorizontalDecoration() ) { d->xActive = true; QApplication::setOverrideCursor(Qt::ClosedHandCursor); event->accept(); return true; } if(d->config.mirrorVertical() && d->verticalHandle.contains(pos) && !d->config.lockVertical() && !d->config.hideVerticalDecoration()) { d->yActive = true; QApplication::setOverrideCursor(Qt::ClosedHandCursor); event->accept(); return true; } } if(event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove) { QMouseEvent *me = dynamic_cast(event); QTabletEvent *te = dynamic_cast(event); QPoint pos = me ? me->pos() : (te ? te->pos() : QPoint(77,77)); if(d->xActive) { float axisX = view()->viewConverter()->widgetToImage(pos).x(); d->setAxisPosition(axisX, d->config.axisPosition().y()); d->config.setHorizontalHandlePosition(KisAlgebra2D::dotProduct(pos - d->horizontalAxis.p1(), d->horizontalAxis.unitVector().p2() - d->horizontalAxis.p1())); emit sigConfigChanged(); event->accept(); return true; } if(d->yActive) { float axisY = view()->viewConverter()->widgetToImage(pos).y(); d->setAxisPosition(d->config.axisPosition().x(), axisY); d->config.setVerticalHandlePosition(KisAlgebra2D::dotProduct(pos - d->verticalAxis.p1(), d->verticalAxis.unitVector().p2() - d->verticalAxis.p1())); emit sigConfigChanged(); event->accept(); return true; } if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) { if(d->horizontalHandle.contains(pos) && !d->config.lockHorizontal()) { if(!d->horizontalContainsCursor) { QApplication::setOverrideCursor(Qt::OpenHandCursor); d->horizontalContainsCursor = true; } } else if(d->horizontalContainsCursor) { QApplication::restoreOverrideCursor(); d->horizontalContainsCursor = false; } } if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) { if(d->verticalHandle.contains(pos) && !d->config.lockVertical()) { if(!d->verticalContainsCursor) { QApplication::setOverrideCursor(Qt::OpenHandCursor); d->verticalContainsCursor = true; } } else if(d->verticalContainsCursor) { QApplication::restoreOverrideCursor(); d->verticalContainsCursor = false; } } } if(event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TabletRelease) { if(d->xActive) { QApplication::restoreOverrideCursor(); d->xActive = false; event->accept(); return true; } if(d->yActive) { QApplication::restoreOverrideCursor(); d->yActive = false; event->accept(); return true; } } return QObject::eventFilter(target, event); } void KisMirrorAxis::mirrorModeChanged() { if (!view()->isCurrent()) { return; } d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal()); d->config.setMirrorVertical(d->resourceProvider->mirrorVertical()); d->config.setLockHorizontal(d->resourceProvider->mirrorHorizontalLock()); d->config.setLockVertical(d->resourceProvider->mirrorVerticalLock()); d->config.setHideHorizontalDecoration(d->resourceProvider->mirrorHorizontalHideDecorations()); d->config.setHideVerticalDecoration(d->resourceProvider->mirrorVerticalHideDecorations()); setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical()); emit sigConfigChanged(); } void KisMirrorAxis::setVisible(bool v) { KisCanvasDecoration::setVisible(v); KisInputManager *inputManager = view() ? view()->canvasBase()->globalInputManager() : 0; if (!inputManager) return; if (v) { inputManager->attachPriorityEventFilter(this); } else { inputManager->detachPriorityEventFilter(this); } } void KisMirrorAxis::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (config != d->config) { KisSignalsBlocker blocker(d->resourceProvider); d->config = config; d->setAxisPosition(d->config.axisPosition().x(), d->config.axisPosition().y()); d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal()); d->resourceProvider->setMirrorVertical(d->config.mirrorVertical()); d->resourceProvider->setMirrorHorizontalLock(d->config.lockHorizontal()); d->resourceProvider->setMirrorVerticalLock(d->config.lockVertical()); d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal()); d->resourceProvider->setMirrorVertical(d->config.mirrorVertical()); d->resourceProvider->setMirrorHorizontalHideDecorations(d->config.hideHorizontalDecoration()); d->resourceProvider->setMirrorVerticalHideDecorations(d->config.hideVerticalDecoration()); } toggleMirrorActions(); setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical()); } const KisMirrorAxisConfig &KisMirrorAxis::mirrorAxisConfig() const { return d->config; } void KisMirrorAxis::toggleMirrorActions() { KActionCollection* collection = view()->viewManager()->actionCollection(); // first uncheck the action, then set according to config; // otherwise the connected KisHighlightedToolButton's highlight color is not // properly set collection->action("hmirror_action")->setChecked(false); collection->action("vmirror_action")->setChecked(false); if (d->config.mirrorHorizontal()) { collection->action("hmirror_action")->setChecked(d->config.mirrorHorizontal()); } if (d->config.mirrorVertical()) { collection->action("vmirror_action")->setChecked(d->config.mirrorVertical()); } collection->action("mirrorX-lock")->setChecked(d->config.lockHorizontal()); collection->action("mirrorY-lock")->setChecked(d->config.lockVertical()); collection->action("mirrorX-hideDecorations")->setChecked(d->config.hideHorizontalDecoration()); collection->action("mirrorY-hideDecorations")->setChecked(d->config.hideVerticalDecoration()); } void KisMirrorAxis::moveHorizontalAxisToCenter() { if (!view()->isCurrent()) { return; } d->setAxisPosition(d->image->width()/2, d->config.axisPosition().y()); emit sigConfigChanged(); } void KisMirrorAxis::moveVerticalAxisToCenter() { if (!view()->isCurrent()) { return; } d->setAxisPosition(d->config.axisPosition().x(), d->image->height()/2 ); emit sigConfigChanged(); } void KisMirrorAxis::Private::setAxisPosition(float x, float y) { QPointF newPosition = QPointF(x, y); config.setAxisPosition(newPosition); const QPointF relativePosition = KisAlgebra2D::absoluteToRelative(newPosition, image->bounds()); image->setMirrorAxesCenter(relativePosition); q->view()->canvasBase()->updateCanvas(); } void KisMirrorAxis::Private::recomputeVisibleAxes(QRect viewport) { KisCoordinatesConverter *converter = q->view()->viewConverter(); config.setAxisPosition(KisAlgebra2D::relativeToAbsolute(image->mirrorAxesCenter(), image->bounds())); QPointF samplePt1 = converter->imageToWidget(config.axisPosition()); QPointF samplePt2 = converter->imageToWidget(QPointF(config.axisPosition().x(), config.axisPosition().y() - 100)); horizontalAxis = QLineF(samplePt1, samplePt2); if (!KisAlgebra2D::intersectLineRect(horizontalAxis, viewport)) horizontalAxis = QLineF(); samplePt2 = converter->imageToWidget(QPointF(config.axisPosition().x() - 100, config.axisPosition().y())); verticalAxis = QLineF(samplePt1, samplePt2); if (!KisAlgebra2D::intersectLineRect(verticalAxis, viewport)) verticalAxis = QLineF(); } diff --git a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp index f900f9169b..451c5819a6 100644 --- a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp @@ -1,385 +1,389 @@ /* * Copyright (c) 2017 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 "KisAsyncAnimationRenderDialogBase.h" #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "KisAsyncAnimationRendererBase.h" #include "kis_time_range.h" #include "kis_image.h" #include "kis_image_config.h" #include "kis_memory_statistics_server.h" #include "kis_signal_compressor.h" #include #include #include namespace { struct RendererPair { std::unique_ptr renderer; KisImageSP image; RendererPair() {} RendererPair(KisAsyncAnimationRendererBase *_renderer, KisImageSP _image) : renderer(_renderer), image(_image) { } RendererPair(RendererPair &&rhs) : renderer(std::move(rhs.renderer)), image(rhs.image) { } }; int calculateNumberMemoryAllowedClones(KisImageSP image) { KisMemoryStatisticsServer::Statistics stats = KisMemoryStatisticsServer::instance() ->fetchMemoryStatistics(image); const qint64 allowedMemory = 0.8 * stats.tilesHardLimit - stats.realMemorySize; const qint64 cloneSize = stats.projectionsSize; - return cloneSize > 0 ? allowedMemory / cloneSize : 0; + if (cloneSize > 0 && allowedMemory > 0) { + return allowedMemory / cloneSize; + } + + return 0; // will become 1; either when the cloneSize = 0 or the allowedMemory is 0 or below } } struct KisAsyncAnimationRenderDialogBase::Private { Private(const QString &_actionTitle, KisImageSP _image, int _busyWait) : actionTitle(_actionTitle), image(_image), busyWait(_busyWait), progressDialogCompressor(40, KisSignalCompressor::FIRST_ACTIVE) { } QString actionTitle; KisImageSP image; int busyWait; bool isBatchMode = false; std::vector asyncRenderers; bool memoryLimitReached = false; QElapsedTimer processingTime; QScopedPointer progressDialog; QEventLoop waitLoop; QList stillDirtyFrames; QList framesInProgress; int dirtyFramesCount = 0; Result result = RenderComplete; QRegion regionOfInterest; KisSignalCompressor progressDialogCompressor; using ProgressData = QPair; boost::optional progressData; int progressDialogReentrancyCounter = 0; int numDirtyFramesLeft() const { return stillDirtyFrames.size() + framesInProgress.size(); } }; KisAsyncAnimationRenderDialogBase::KisAsyncAnimationRenderDialogBase(const QString &actionTitle, KisImageSP image, int busyWait) : m_d(new Private(actionTitle, image, busyWait)) { connect(&m_d->progressDialogCompressor, SIGNAL(timeout()), SLOT(slotUpdateCompressedProgressData()), Qt::QueuedConnection); } KisAsyncAnimationRenderDialogBase::~KisAsyncAnimationRenderDialogBase() { } KisAsyncAnimationRenderDialogBase::Result KisAsyncAnimationRenderDialogBase::regenerateRange(KisViewManager *viewManager) { { /** * Since this method can be called from the places where no * view manager is available, we need this manually crafted * ugly construction to "try-lock-cancel" the image. */ bool imageIsIdle = true; if (viewManager) { imageIsIdle = viewManager->blockUntilOperationsFinished(m_d->image); } else { imageIsIdle = false; if (m_d->image->tryBarrierLock(true)) { m_d->image->unlock(); imageIsIdle = true; } } if (!imageIsIdle) { return RenderCancelled; } } m_d->stillDirtyFrames = calcDirtyFrames(); m_d->framesInProgress.clear(); m_d->result = RenderComplete; m_d->dirtyFramesCount = m_d->stillDirtyFrames.size(); if (!m_d->isBatchMode) { QWidget *parentWidget = viewManager ? viewManager->mainWindow() : 0; m_d->progressDialog.reset(new QProgressDialog(m_d->actionTitle, i18n("Cancel"), 0, 0, parentWidget)); m_d->progressDialog->setWindowModality(Qt::ApplicationModal); m_d->progressDialog->setMinimum(0); m_d->progressDialog->setMaximum(m_d->dirtyFramesCount); m_d->progressDialog->setMinimumDuration(m_d->busyWait); connect(m_d->progressDialog.data(), SIGNAL(canceled()), SLOT(slotCancelRegeneration())); } if (m_d->dirtyFramesCount <= 0) return m_d->result; m_d->processingTime.start(); KisImageConfig cfg(true); const int maxThreads = cfg.maxNumberOfThreads(); const int numAllowedWorker = 1 + calculateNumberMemoryAllowedClones(m_d->image); const int proposedNumWorkers = qMin(m_d->dirtyFramesCount, cfg.frameRenderingClones()); const int numWorkers = qMin(proposedNumWorkers, numAllowedWorker); const int numThreadsPerWorker = qMax(1, qCeil(qreal(maxThreads) / numWorkers)); m_d->memoryLimitReached = numWorkers < proposedNumWorkers; const int oldWorkingThreadsLimit = m_d->image->workingThreadsLimit(); ENTER_FUNCTION() << ppVar(numWorkers) << ppVar(numThreadsPerWorker); for (int i = 0; i < numWorkers; i++) { // reuse the image for one of the workers KisImageSP image = i == numWorkers - 1 ? m_d->image : m_d->image->clone(true); image->setWorkingThreadsLimit(numThreadsPerWorker); KisAsyncAnimationRendererBase *renderer = createRenderer(image); connect(renderer, SIGNAL(sigFrameCompleted(int)), SLOT(slotFrameCompleted(int))); connect(renderer, SIGNAL(sigFrameCancelled(int)), SLOT(slotFrameCancelled(int))); m_d->asyncRenderers.push_back(RendererPair(renderer, image)); } ENTER_FUNCTION() << "Copying done in" << m_d->processingTime.elapsed(); tryInitiateFrameRegeneration(); updateProgressLabel(); if (m_d->numDirtyFramesLeft() > 0) { m_d->waitLoop.exec(); } ENTER_FUNCTION() << "Full regeneration done in" << m_d->processingTime.elapsed(); for (auto &pair : m_d->asyncRenderers) { KIS_SAFE_ASSERT_RECOVER_NOOP(!pair.renderer->isActive()); if (viewManager) { viewManager->blockUntilOperationsFinishedForced(pair.image); } else { pair.image->barrierLock(true); pair.image->unlock(); } } m_d->asyncRenderers.clear(); if (viewManager) { viewManager->blockUntilOperationsFinishedForced(m_d->image); } else { m_d->image->barrierLock(true); m_d->image->unlock(); } m_d->image->setWorkingThreadsLimit(oldWorkingThreadsLimit); m_d->progressDialog.reset(); return m_d->result; } void KisAsyncAnimationRenderDialogBase::setRegionOfInterest(const QRegion &roi) { m_d->regionOfInterest = roi; } QRegion KisAsyncAnimationRenderDialogBase::regionOfInterest() const { return m_d->regionOfInterest; } void KisAsyncAnimationRenderDialogBase::slotFrameCompleted(int frame) { Q_UNUSED(frame); m_d->framesInProgress.removeOne(frame); tryInitiateFrameRegeneration(); updateProgressLabel(); } void KisAsyncAnimationRenderDialogBase::slotFrameCancelled(int frame) { Q_UNUSED(frame); cancelProcessingImpl(false); } void KisAsyncAnimationRenderDialogBase::slotCancelRegeneration() { cancelProcessingImpl(true); } void KisAsyncAnimationRenderDialogBase::cancelProcessingImpl(bool isUserCancelled) { for (auto &pair : m_d->asyncRenderers) { if (pair.renderer->isActive()) { pair.renderer->cancelCurrentFrameRendering(); } KIS_SAFE_ASSERT_RECOVER_NOOP(!pair.renderer->isActive()); } m_d->stillDirtyFrames.clear(); m_d->framesInProgress.clear(); m_d->result = isUserCancelled ? RenderCancelled : RenderFailed; updateProgressLabel(); } void KisAsyncAnimationRenderDialogBase::tryInitiateFrameRegeneration() { bool hadWorkOnPreviousCycle = false; while (!m_d->stillDirtyFrames.isEmpty()) { for (auto &pair : m_d->asyncRenderers) { if (!pair.renderer->isActive()) { const int currentDirtyFrame = m_d->stillDirtyFrames.takeFirst(); initializeRendererForFrame(pair.renderer.get(), pair.image, currentDirtyFrame); pair.renderer->startFrameRegeneration(pair.image, currentDirtyFrame, m_d->regionOfInterest); hadWorkOnPreviousCycle = true; m_d->framesInProgress.append(currentDirtyFrame); break; } } if (!hadWorkOnPreviousCycle) break; hadWorkOnPreviousCycle = false; } } void KisAsyncAnimationRenderDialogBase::updateProgressLabel() { const int processedFramesCount = m_d->dirtyFramesCount - m_d->numDirtyFramesLeft(); const qint64 elapsedMSec = m_d->processingTime.elapsed(); const qint64 estimatedMSec = !processedFramesCount ? 0 : elapsedMSec * m_d->dirtyFramesCount / processedFramesCount; const QTime elapsedTime = QTime::fromMSecsSinceStartOfDay(elapsedMSec); const QTime estimatedTime = QTime::fromMSecsSinceStartOfDay(estimatedMSec); const QString timeFormat = estimatedTime.hour() > 0 ? "HH:mm:ss" : "mm:ss"; const QString elapsedTimeString = elapsedTime.toString(timeFormat); const QString estimatedTimeString = estimatedTime.toString(timeFormat); const QString memoryLimitMessage( i18n("\n\nThe memory limit has been reached.\nThe number of frames saved simultaneously is limited to %1\n\n", m_d->asyncRenderers.size())); const QString progressLabel(i18n("%1\n\nElapsed: %2\nEstimated: %3\n\n%4", m_d->actionTitle, elapsedTimeString, estimatedTimeString, m_d->memoryLimitReached ? memoryLimitMessage : QString())); if (m_d->progressDialog) { /** * We should avoid reentrancy caused by explicit * QApplication::processEvents() in QProgressDialog::setValue(), so use * a compressor instead */ m_d->progressData = Private::ProgressData(processedFramesCount, progressLabel); m_d->progressDialogCompressor.start(); } if (!m_d->numDirtyFramesLeft()) { m_d->waitLoop.quit(); } } void KisAsyncAnimationRenderDialogBase::slotUpdateCompressedProgressData() { /** * Qt's implementation of QProgressDialog is a bit weird: it calls * QApplication::processEvents() from inside setValue(), which means * that our update method may reenter multiple times. * * This code avoids reentering by using a compresson and an explicit * entrance counter. */ if (m_d->progressDialogReentrancyCounter > 0) { m_d->progressDialogCompressor.start(); return; } if (m_d->progressDialog && m_d->progressData) { m_d->progressDialogReentrancyCounter++; m_d->progressDialog->setLabelText(m_d->progressData->second); m_d->progressDialog->setValue(m_d->progressData->first); m_d->progressData = boost::none; m_d->progressDialogReentrancyCounter--; } } void KisAsyncAnimationRenderDialogBase::setBatchMode(bool value) { m_d->isBatchMode = value; } bool KisAsyncAnimationRenderDialogBase::batchMode() const { return m_d->isBatchMode; } diff --git a/libs/ui/dialogs/kis_dlg_adjustment_layer.cc b/libs/ui/dialogs/kis_dlg_adjustment_layer.cc index 2a35ddc66f..564c2efac2 100644 --- a/libs/ui/dialogs/kis_dlg_adjustment_layer.cc +++ b/libs/ui/dialogs/kis_dlg_adjustment_layer.cc @@ -1,140 +1,140 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2008 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 "kis_dlg_adjustment_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include "filter/kis_filter.h" #include "kis_config_widget.h" #include "filter/kis_filter_configuration.h" #include "kis_paint_device.h" #include "kis_transaction.h" #include "kis_node.h" #include "kis_node_filter_interface.h" #include #include "KisViewManager.h" KisDlgAdjustmentLayer::KisDlgAdjustmentLayer(KisNodeSP node, KisNodeFilterInterface* nfi, KisPaintDeviceSP paintDevice, const QString &layerName, const QString &caption, KisViewManager *view, QWidget *parent) - : KoDialog(parent) + : KoDialog(parent, Qt::Dialog) , m_node(node) , m_nodeFilterInterface(nfi) , m_currentFilter(0) , m_customName(false) , m_layerName(layerName) { setCaption(caption); setButtons(None); QWidget * page = new QWidget(this); wdgFilterNodeCreation.setupUi(page); setMainWidget(page); wdgFilterNodeCreation.filterGalleryToggle->setChecked(wdgFilterNodeCreation.filterSelector->isFilterGalleryVisible()); wdgFilterNodeCreation.filterGalleryToggle->setIcon(QPixmap(":/pics/sidebaricon.png")); wdgFilterNodeCreation.filterGalleryToggle->setMaximumWidth(wdgFilterNodeCreation.filterGalleryToggle->height()); connect(wdgFilterNodeCreation.filterSelector, SIGNAL(sigFilterGalleryToggled(bool)), wdgFilterNodeCreation.filterGalleryToggle, SLOT(setChecked(bool))); connect(wdgFilterNodeCreation.filterGalleryToggle, SIGNAL(toggled(bool)), wdgFilterNodeCreation.filterSelector, SLOT(showFilterGallery(bool))); connect(wdgFilterNodeCreation.filterSelector, SIGNAL(sigSizeChanged()), this, SLOT(slotFilterWidgetSizeChanged())); connect(wdgFilterNodeCreation.buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(wdgFilterNodeCreation.buttonBox, SIGNAL(rejected()), this, SLOT(reject())); wdgFilterNodeCreation.filterSelector->setView(view); wdgFilterNodeCreation.filterSelector->showFilterGallery(KisConfig(true).showFilterGalleryLayerMaskDialog()); wdgFilterNodeCreation.filterSelector->setPaintDevice(false, paintDevice); wdgFilterNodeCreation.layerName->setText(layerName); connect(wdgFilterNodeCreation.filterSelector, SIGNAL(configurationChanged()), SLOT(slotConfigChanged())); connect(wdgFilterNodeCreation.layerName, SIGNAL(textChanged(QString)), SLOT(slotNameChanged(QString))); slotConfigChanged(); } KisDlgAdjustmentLayer::~KisDlgAdjustmentLayer() { KisConfig(true).setShowFilterGalleryLayerMaskDialog(wdgFilterNodeCreation.filterSelector->isFilterGalleryVisible()); } void KisDlgAdjustmentLayer::slotNameChanged(const QString &text) { Q_UNUSED(text); m_customName = !text.isEmpty(); enableButtonOk(m_currentFilter); } KisFilterConfigurationSP KisDlgAdjustmentLayer::filterConfiguration() const { KisFilterConfigurationSP config = wdgFilterNodeCreation.filterSelector->configuration(); Q_ASSERT(config); return config; } QString KisDlgAdjustmentLayer::layerName() const { return wdgFilterNodeCreation.layerName->text(); } void KisDlgAdjustmentLayer::slotConfigChanged() { m_currentFilter = filterConfiguration(); enableButtonOk(m_currentFilter); if (m_currentFilter) { m_nodeFilterInterface->setFilter(m_currentFilter); if (!m_customName) { wdgFilterNodeCreation.layerName->blockSignals(true); wdgFilterNodeCreation.layerName->setText(m_layerName + " (" + wdgFilterNodeCreation.filterSelector->currentFilter()->name() + ")"); wdgFilterNodeCreation.layerName->blockSignals(false); } } m_node->setDirty(); } void KisDlgAdjustmentLayer::adjustSize() { QWidget::adjustSize(); } void KisDlgAdjustmentLayer::slotFilterWidgetSizeChanged() { QMetaObject::invokeMethod(this, "adjustSize", Qt::QueuedConnection); } diff --git a/libs/ui/flake/KisReferenceImagesLayer.cpp b/libs/ui/flake/KisReferenceImagesLayer.cpp index 413ffd9005..cfb78dc163 100644 --- a/libs/ui/flake/KisReferenceImagesLayer.cpp +++ b/libs/ui/flake/KisReferenceImagesLayer.cpp @@ -1,215 +1,218 @@ /* * Copyright (C) 2017 Jouni Pentikäinen * * 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 #include #include #include #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include "KisDocument.h" struct AddReferenceImagesCommand : KoShapeCreateCommand { AddReferenceImagesCommand(KisDocument *document, KisSharedPtr layer, const QList referenceImages) : KoShapeCreateCommand(layer->shapeController(), referenceImages, layer.data(), nullptr, kundo2_i18n("Add reference image")) , m_document(document) , m_layer(layer) {} void redo() override { auto layer = m_document->referenceImagesLayer(); KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer) if (!layer) { m_document->setReferenceImagesLayer(m_layer, true); } KoShapeCreateCommand::redo(); } void undo() override { KoShapeCreateCommand::undo(); if (m_layer->shapeCount() == 0) { m_document->setReferenceImagesLayer(nullptr, true); } } private: KisDocument *m_document; KisSharedPtr m_layer; }; struct RemoveReferenceImagesCommand : KoShapeDeleteCommand { RemoveReferenceImagesCommand(KisDocument *document, KisSharedPtr layer, QList referenceImages) : KoShapeDeleteCommand(layer->shapeController(), referenceImages) , m_document(document) , m_layer(layer) {} void redo() override { KoShapeDeleteCommand::redo(); if (m_layer->shapeCount() == 0) { m_document->setReferenceImagesLayer(nullptr, true); } } void undo() override { auto layer = m_document->referenceImagesLayer(); KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer) if (!layer) { m_document->setReferenceImagesLayer(m_layer, true); } KoShapeDeleteCommand::undo(); } private: KisDocument *m_document; KisSharedPtr m_layer; }; class ReferenceImagesCanvas : public KisShapeLayerCanvasBase { public: ReferenceImagesCanvas(KisReferenceImagesLayer *parent, KisImageWSP image) : KisShapeLayerCanvasBase(parent, image) , m_layer(parent) {} void updateCanvas(const QRectF &rect) override { if (!m_layer->image() || m_isDestroying) { return; } QRectF r = m_viewConverter->documentToView(rect); m_layer->signalUpdate(r); } void forceRepaint() override { m_layer->signalUpdate(m_layer->boundingImageRect()); } bool hasPendingUpdates() const override { return false; } void rerenderAfterBeingInvisible() override {} void resetCache() override {} - void setImage(KisImageWSP /*image*/) override {} + void setImage(KisImageWSP image) override + { + m_viewConverter->setImage(image); + } private: KisReferenceImagesLayer *m_layer; }; KisReferenceImagesLayer::KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image) : KisShapeLayer(shapeController, image, i18n("Reference images"), OPACITY_OPAQUE_U8, new ReferenceImagesCanvas(this, image)) {} KisReferenceImagesLayer::KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs) : KisShapeLayer(rhs, rhs.shapeController(), new ReferenceImagesCanvas(this, rhs.image())) {} KUndo2Command * KisReferenceImagesLayer::addReferenceImages(KisDocument *document, const QList referenceImages) { KisSharedPtr layer = document->referenceImagesLayer(); if (!layer) { layer = new KisReferenceImagesLayer(document->shapeController(), document->image()); } return new AddReferenceImagesCommand(document, layer, referenceImages); } KUndo2Command * KisReferenceImagesLayer::removeReferenceImages(KisDocument *document, QList referenceImages) { return new RemoveReferenceImagesCommand(document, this, referenceImages); } QVector KisReferenceImagesLayer::referenceImages() const { QVector references; Q_FOREACH(auto shape, shapes()) { KisReferenceImage *referenceImage = dynamic_cast(shape); if (referenceImage) { references.append(referenceImage); } } return references; } void KisReferenceImagesLayer::paintReferences(QPainter &painter) { shapeManager()->paint(painter, *converter(), false); } bool KisReferenceImagesLayer::allowAsChild(KisNodeSP) const { return false; } bool KisReferenceImagesLayer::accept(KisNodeVisitor &visitor) { return visitor.visit(this); } void KisReferenceImagesLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { visitor.visit(this, undoAdapter); } bool KisReferenceImagesLayer::isFakeNode() const { return true; } void KisReferenceImagesLayer::signalUpdate(const QRectF &rect) { emit sigUpdateCanvas(rect); } QRectF KisReferenceImagesLayer::boundingImageRect() const { return converter()->documentToView(boundingRect()); } QColor KisReferenceImagesLayer::getPixel(QPointF position) const { const QPointF docPoint = converter()->viewToDocument(position); KoShape *shape = shapeManager()->shapeAt(docPoint); if (shape) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, QColor()); return reference->getPixel(docPoint); } return QColor(); } diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index 061547d1e4..88d2331ab4 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,754 +1,761 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * 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_input_manager.h" #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_pan_action.h" #include "kis_alternate_invocation_action.h" #include "kis_rotate_canvas_action.h" #include "kis_zoom_action.h" #include "kis_show_palette_action.h" #include "kis_change_primary_setting_action.h" #include "kis_shortcut_matcher.h" #include "kis_stroke_shortcut.h" #include "kis_single_action_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile.h" #include "kis_input_profile_manager.h" #include "kis_shortcut_configuration.h" #include #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool())); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { d->allowMouseEvents(); } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &filter = it->second; if (filter.isNull()) { it = d->priorityEventFilter.erase(it); d->priorityEventFilterSeqNo++; savedPriorityEventFilterSeqNo++; continue; } if (filter->eventFilter(object, event)) return true; /** * If the filter removed itself from the filters list or * added something there, just exit the loop */ if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) { return true; } ++it; } // KoToolProxy needs to pre-process some events to ensure the // global shortcuts (not the input manager's ones) are not // executed, in particular, this line will accept events when the // tool is in text editing, preventing shortcut triggering if (d->toolProxy) { d->toolProxy->processEvent(event); } } // Continue with the actual switch statement... return eventFilterImpl(event); } // Qt's events do not have copy-ctors yes, so we should emulate them // See https://bugreports.qt.io/browse/QTBUG-72488 template void copyEventHack(Event *src, QScopedPointer &dst); template<> void copyEventHack(QMouseEvent *src, QScopedPointer &dst) { QMouseEvent *tmp = new QMouseEvent(src->type(), src->localPos(), src->windowPos(), src->screenPos(), src->button(), src->buttons(), src->modifiers(), src->source()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template<> void copyEventHack(QTabletEvent *src, QScopedPointer &dst) { QTabletEvent *tmp = new QTabletEvent(src->type(), src->posF(), src->globalPosF(), src->device(), src->pointerType(), src->pressure(), src->xTilt(), src->yTilt(), src->tangentialPressure(), src->rotation(), src->z(), src->modifiers(), src->uniqueId(), src->button(), src->buttons()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template bool KisInputManager::compressMoveEventCommon(Event *event) { /** * We construct a copy of this event object, so we must ensure it * has a correct type. */ static_assert(std::is_same::value || std::is_same::value, "event should be a mouse or a tablet event"); bool retval = false; /** * Compress the events if the tool doesn't need high resolution input */ if ((event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove) && (!d->matcher.supportsHiResInputEvents() || d->testingCompressBrushEvents)) { copyEventHack(event, d->compressedMoveEvent); d->moveEventCompressor.start(); /** * On Linux Qt eats the rest of unneeded events if we * ignore the first of the chunk of tablet events. So * generally we should never activate this feature. Only * for testing purposes! */ if (d->testingAcceptCompressedTabletEvents) { event->setAccepted(true); } retval = true; } else { slotCompressedMoveEvent(); retval = d->handleCompressedTabletEvent(event); } return retval; } bool shouldResetWheelDelta(QEvent * event) { return event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease || event->type() == QEvent::Enter || event->type() == QEvent::Leave || event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel || event->type() == QEvent::NativeGesture; } bool KisInputManager::eventFilterImpl(QEvent * event) { bool retval = false; if (shouldResetWheelDelta(event)) { d->accumulatedScrollDelta = 0; } switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent); } //Reset signal compressor to prevent processing events before press late d->resetCompressor(); event->setAccepted(retval); break; } case QEvent::MouseButtonRelease: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); if (!keyEvent->isAutoRepeat()) { retval = d->matcher.keyPressed(key); } else { retval = d->matcher.autoRepeatedKeyPressed(key); } /** * Workaround for temporary switching of tools by * KoCanvasControllerWidget. We don't need this switch because * we handle it ourselves. */ retval |= !d->forwardAllEventsToTool && (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Escape); break; } case QEvent::KeyRelease: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); QMouseEvent *mouseEvent = static_cast(event); retval = compressMoveEventCommon(mouseEvent); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); #ifdef Q_OS_MACOS // Some QT wheel events are actually touch pad pan events. From the QT docs: // "Wheel events are generated for both mouse wheels and trackpad scroll gestures." // We differentiate between touchpad events and real mouse wheels by inspecting the // event source. if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent); break; } #endif d->accumulatedScrollDelta += wheelEvent->delta(); KisSingleActionShortcut::WheelAction action; /** * Ignore delta 0 events on OSX, since they are triggered by tablet * proximity when using Wacom devices. */ #ifdef Q_OS_MACOS if(wheelEvent->delta() == 0) { retval = true; break; } #endif if (wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); d->accumulatedScrollDelta = 0; } else { retval = true; } break; } case QEvent::Enter: d->debugEvent(event); //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); if (!d->containsPointer) { d->containsPointer = true; d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; } d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); eventFilterImpl(&kevent); } } d->allowMouseEvents(); break; case QEvent::FocusOut: { d->debugEvent(event); KisAbstractInputAction::setInputManager(this); QPointF currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); d->matcher.lostFocusEvent(currentLocalPos); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); if (!d->containsPointer) { d->containsPointer = true; d->touchHasBlockedPressEvents = false; } } event->setAccepted(true); retval = true; d->blockMouseEvents(); //Reset signal compressor to prevent processing events before press late d->resetCompressor(); - d->eatOneMousePress(); + #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); + d->eatOneMousePress(); +#elif defined Q_OS_WIN32 + /** + * Windows is the only platform that synthesizes mouse events for + * the tablet on OS-level, that is, even when we accept the event + */ + d->eatOneMousePress(); #endif break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = compressMoveEventCommon(tabletEvent); if (d->tabletLatencyTracker) { d->tabletLatencyTracker->push(tabletEvent->timestamp()); } /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the * TabletEnterProximity event was missed (may happen when * changing focus of the window with tablet in the proximity * area) */ d->blockMouseEvents(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TabletRelease: { #ifdef Q_OS_MAC d->allowMouseEvents(); #endif d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TouchBegin: { if (startTouch(retval)) { QTouchEvent *tevent = static_cast(event); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchBeginEvent(tevent); event->accept(); } break; } case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); #ifdef Q_OS_MAC int count = 0; Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; } } if (count < 2 && tevent->touchPoints().length() > count) { d->touchHasBlockedPressEvents = false; retval = d->matcher.touchEndEvent(tevent); } else { #endif d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchUpdateEvent(tevent); #ifdef Q_OS_MACOS } #endif event->accept(); break; } case QEvent::TouchEnd: { endTouch(); QTouchEvent *tevent = static_cast(event); retval = d->matcher.touchEndEvent(tevent); event->accept(); break; } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); switch (gevent->gestureType()) { case Qt::BeginNativeGesture: { if (startTouch(retval)) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureBeginEvent(gevent); event->accept(); } break; } case Qt::EndNativeGesture: { endTouch(); retval = d->matcher.nativeGestureEndEvent(gevent); event->accept(); break; } default: { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureEvent(gevent); event->accept(); break; } } break; } default: break; } return !retval ? d->processUnhandledEvent(event) : true; } bool KisInputManager::startTouch(bool &retval) { d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); // Touch rejection: if touch is disabled on canvas, no need to block mouse press events if (KisConfig(true).disableTouchOnCanvas()) { d->eatOneMousePress(); } if (d->tryHidePopupPalette()) { retval = true; return false; } else { return true; } } void KisInputManager::endTouch() { d->touchHasBlockedPressEvents = false; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // d->touchHasBlockedPressEvents = false; (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); //dbgInput << "Compressed move event received."; } else { //dbgInput << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } QPointer KisInputManager::toolProxy() const { return d->toolProxy; } void KisInputManager::slotAboutToChangeTool() { QPointF currentLocalPos; if (canvas() && canvas()->canvasWidget()) { currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); } d->matcher.lostFocusEvent(currentLocalPos); } void KisInputManager::slotToolChanged() { if (!d->canvas) return; KoToolManager *toolManager = KoToolManager::instance(); KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId()); if (tool) { d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); if (tool->isInTextMode()) { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } } } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList shortcuts = profile->allShortcuts(); for (KisShortcutConfiguration * const shortcut : shortcuts) { dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name(); switch(shortcut->type()) { case KisShortcutConfiguration::KeyCombinationType: d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys()); break; case KisShortcutConfiguration::MouseButtonType: d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons()); break; case KisShortcutConfiguration::MouseWheelType: d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel()); break; case KisShortcutConfiguration::GestureType: if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) { d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture()); } break; default: break; } } } else { dbgInput << "No Input Profile Found: canvas interaction will be impossible"; } } diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 62fa1804a2..b81b35d856 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2149 +1,2151 @@ /* * Copyright (c) 2002 Patrick Julien * * 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_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" #endif KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp && qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const { return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0); } void KisConfig::setPreferredVectorImportResolutionPPI(int value) const { m_cfg.writeEntry("preferredVectorImportResolution", value); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QString KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); KoColor kol(KoColorSpaceRegistry::instance()->rgb8()); kol.fromQColor(col); QString xml = kol.toXML(); return (defaultValue ? xml : m_cfg.readEntry("mdiBackgroundColorXML", xml)); } void KisConfig::setMDIBackgroundColor(const QString &v) const { m_cfg.writeEntry("mdiBackgroundColorXML", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg(true); QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::forcePaletteColors(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false)); } void KisConfig::setForcePaletteColors(bool forcePaletteColors) { m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString cs = canvasState(); #ifdef Q_OS_WIN return (m_cfg.readEntry("useOpenGLWindows", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #else return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #endif } void KisConfig::setUseOpenGL(bool useOpenGL) const { #ifdef Q_OS_WIN m_cfg.writeEntry("useOpenGLWindows", useOpenGL); #else m_cfg.writeEntry("useOpenGL", useOpenGL); #endif } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); m_cfg.writeEntry("globalSnapToPixel", config.toPixel()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return useWin8PointerInputNoApp(&kritarc, defaultValue); #else return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #endif #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setUseWin8PointerInputNoApp(&kritarc, value); #else m_cfg.writeEntry("useWin8PointerInput", value); #endif } #else Q_UNUSED(value) #endif } bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue) { return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool(); } void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value) { settings->setValue("useWin8PointerInput", value); } bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false)); } void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value) { m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { - return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); + return (defaultValue ? QStringList() : + m_cfg.readEntry("favoriteCompositeOps", + QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(','))); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); const QString xmlData = exportConfigurationXML(filterId, defaultValue); cfg->fromXML(xmlData); return cfg; } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const { KisOcioConfiguration cfg; if (!defaultValue) { cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0); cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()); cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()); cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString()); cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString()); cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString()); cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString()); } return cfg; } void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg) { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode); m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath); m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath); m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace); m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice); m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView); m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } int KisConfig::layerThumbnailSize(bool defaultValue) const { return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20)); } void KisConfig::setLayerThumbnailSize(int size) { m_cfg.writeEntry("layerThumbnailSize", size); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } bool KisConfig::kineticScrollingEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true)); } void KisConfig::setKineticScrollingEnabled(bool value) { m_cfg.writeEntry("KineticScrollingEnabled", value); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2)); } void KisConfig::setKineticScrollingGesture(int gesture) { m_cfg.writeEntry("KineticScrollingGesture", gesture); } int KisConfig::kineticScrollingSensitivity(bool defaultValue) const { return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); } void KisConfig::setKineticScrollingSensitivity(int sensitivity) { m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); } bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false)); } void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar) { m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setDisableAVXOptimizations(bool value) { m_cfg.writeEntry("disableAVXOptimizations", value); } bool KisConfig::disableAVXOptimizations(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } QColor KisConfig::defaultAssistantsColor(bool defaultValue) const { static const QColor defaultColor = QColor(176, 176, 176, 255); return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor); } void KisConfig::setDefaultAssistantsColor(const QColor &color) const { m_cfg.writeEntry("defaultAssistantsColor", color); } bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false); } void KisConfig::setAutoSmoothBezierCurves(bool value) { m_cfg.writeEntry("autoSmoothBezierCurves", value); } bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false); } void KisConfig::setActivateTransformToolAfterPaste(bool value) { m_cfg.writeEntry("activateTransformToolAfterPaste", value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return rootSurfaceFormat(&kritarc, defaultValue); } void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setRootSurfaceFormat(&kritarc, value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue) { QString textValue = "bt709-g22"; if (!defaultValue) { textValue = displayrc->value("rootSurfaceFormat", textValue).toString(); } return textValue == "bt709-g10" ? BT709_G10 : textValue == "bt2020-pq" ? BT2020_PQ : BT709_G22; } void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value) { const QString textValue = value == BT709_G10 ? "bt709-g10" : value == BT2020_PQ ? "bt2020-pq" : "bt709-g22"; displayrc->setValue("rootSurfaceFormat", textValue); } bool KisConfig::useZip64(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("UseZip64", false); } void KisConfig::setUseZip64(bool value) { m_cfg.writeEntry("UseZip64", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index 1bdce51c3d..c5c25ae838 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,999 +1,999 @@ /* * Copyright (C) 2006 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 "kis_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "dialogs/KisDlgChangeCloneSource.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_node_commands.h" #include "kis_change_file_layer_command.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_processing_applicator.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_imageFlatten(0) , m_imageMergeLayer(0) , m_groupLayersSave(0) , m_imageResizeToLayer(0) , m_flattenLayer(0) , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) , m_layerStyle(0) { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { emit sigLayerActivated(layer); layersUpdated(); if (layer) { m_view->canvasResourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageSP image = m_view->image(); KisLayerSP layer = activeLayer(); const bool isGroupLayer = layer && layer->inherits("KisGroupLayer"); m_imageMergeLayer->setText( isGroupLayer ? i18nc("@action:inmenu", "Merge Group") : i18nc("@action:inmenu", "Merge with Layer Below")); m_flattenLayer->setVisible(!isGroupLayer); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); if (!layer) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP groupLayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); KisFileLayerSP filterLayer = KisFileLayerSP(dynamic_cast(layer.data())); if (adjustmentLayer && !multipleLayersSelected) { KisPaintDeviceSP dev = adjustmentLayer->projection(); KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(adjustmentLayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { adjustmentLayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(adjustmentLayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); adjustmentLayer->setDirty(); } } } else if (groupLayer && !multipleLayersSelected) { KisFilterConfigurationSP configBefore(groupLayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(groupLayer->name(), m_view, m_view->mainWindow(), groupLayer, configBefore); dlg->setCaption(i18n("Fill Layer Properties")); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setConfiguration(configBefore.data()); dlg->resize(dlg->minimumSizeHint()); Qt::WindowFlags flags = dlg->windowFlags(); dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dlg->show(); } else if (filterLayer && !multipleLayersSelected){ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString fileNameOld = filterLayer->fileName(); KisFileLayer::ScalingMethod scalingMethodOld = filterLayer->scalingMethod(); KisDlgFileLayer dlg(basePath, filterLayer->name(), m_view->mainWindow()); dlg.setCaption(i18n("File Layer Properties")); dlg.setFileName(fileNameOld); dlg.setScalingMethod(scalingMethodOld); if (dlg.exec() == QDialog::Accepted) { const QString fileNameNew = dlg.fileName(); KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution(); if(fileNameNew.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } filterLayer->setName(dlg.layerName()); if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) { KisChangeFileLayerCmd *cmd = new KisChangeFileLayerCmd(filterLayer, basePath, fileNameOld, scalingMethodOld, basePath, fileNameNew, scalingMethodNew); m_view->undoAdapter()->addCommand(cmd); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } } void KisLayerManager::changeCloneSource() { QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.isEmpty()) { return; } QList cloneLayers; KisNodeSP node; Q_FOREACH (node, selectedNodes) { KisCloneLayerSP cloneLayer(qobject_cast(node.data())); if (cloneLayer) { cloneLayers << cloneLayer; } } if (cloneLayers.isEmpty()) { return; } KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view); dialog->setCaption(i18n("Change Clone Layer")); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source->prevSibling(); while (parent && !parent->allowAsChild(layer)) { above = above ? above->parent() : source->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::convertLayerToFileLayer(KisNodeSP source) { KisImageSP image = m_view->image(); if (!image) return; QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); dlg.setWindowTitle(i18n("Save layers to...")); QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location.")); lbl->setWordWrap(true); layout->addWidget(lbl); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); if (m_view->document()->url().isLocalFile()) { QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).baseName(); location.setFile(location.dir(), location.baseName() + "_" + source->name() + ".png"); urlRequester->setFileName(location.absoluteFilePath()); } else { const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png"); urlRequester->setFileName(proposedFileName); } // We don't want .kra files as file layers, Krita cannot handle the load. QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); int i = mimes.indexOf(KIS_MIME_TYPE); if (i >= 0 && i < mimes.size()) { mimes.removeAt(i); } urlRequester->setMimeTypeFilters(mimes); layout->addWidget(urlRequester); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QScopedPointer doc(KisPart::instance()->createDocument()); QRect bounds = source->exactBounds(); KisImageSP dst = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->projection()->compositionSourceColorSpace(), source->name()); dst->setResolution(image->xRes(), image->yRes()); doc->setFileBatchMode(false); doc->setCurrentImage(dst); KisNodeSP node = source->clone(); dst->addNode(node); dst->initialRefreshGraph(); dst->cropImage(bounds); dst->waitForDone(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1()); if (!r) { qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage(); } else { QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString relativePath = QDir(basePath).relativeFilePath(path); KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8); fileLayer->setX(bounds.x()); fileLayer->setY(bounds.y()); KisNodeSP dstParent = source->parent(); KisNodeSP dstAboveThis = source->prevSibling(); m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis); m_commandsAdapter->endMacro(); } doc->closeUrl(false); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); if (parent->inherits("KisGroupLayer") && parent->collapsed()) { above = parent; parent = parent->parent(); return; } while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator); } KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, group, false, 0); return group; } KisNodeSP KisLayerManager::addCloneLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisNodeSP node = new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node, true, 0); return node; } KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return 0; if (!m_view->document()) return 0; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Add Layer")); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); - KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view); + KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow()); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! applicator.cancel(); } else { adjl->setName(dlg.layerName()); applicator.end(); } return adjl; } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer, true, applicator); return layer; } KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor(); KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0); KisFilterConfigurationSP defaultConfig = dlg.configuration(); defaultConfig->setProperty("color", currentForeground); dlg.setConfiguration(defaultConfig); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void KisLayerManager::flattenImage() { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(m_view->activeNode()); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image) { bool result = false; if (currentNode->inherits("KisGroupLayer")) { KisGroupLayer *layer = qobject_cast(currentNode.data()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false); image->flattenLayer(layer); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (tryMergeSelectionMasks(m_view->activeNode(), image)) { // already done! } else if (tryFlattenGroupLayer(m_view->activeNode(), image)) { // already done! } else { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (prevLayer->userLocked()) { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is locked "), QIcon(), 2000, KisFloatingMessage::Low); } else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; convertNodeToPaintLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.baseName(); KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return 0; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->canvasResourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kis_mask_manager.cc b/libs/ui/kis_mask_manager.cc index 762383e79d..ff32ca0636 100644 --- a/libs/ui/kis_mask_manager.cc +++ b/libs/ui/kis_mask_manager.cc @@ -1,365 +1,366 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006 * * 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_mask_manager.h" #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include "dialogs/kis_dlg_adjustment_layer.h" #include "widgets/kis_mask_widgets.h" #include #include #include #include "dialogs/kis_dlg_adj_layer_props.h" #include #include #include #include #include "kis_node_commands_adapter.h" #include "commands/kis_deselect_global_selection_command.h" #include "kis_iterator_ng.h" KisMaskManager::KisMaskManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) { } void KisMaskManager::setView(QPointerimageView) { m_imageView = imageView; } void KisMaskManager::setup(KActionCollection *actionCollection, KisActionManager *actionManager) { Q_UNUSED(actionCollection); Q_UNUSED(actionManager); } void KisMaskManager::updateGUI() { // XXX: enable/disable menu items according to whether there's a mask selected currently // XXX: disable the selection mask item if there's already a selection mask // YYY: doesn't KisAction do that already? } KisMaskSP KisMaskManager::activeMask() { if (m_imageView) { return m_imageView->currentMask(); } return 0; } KisPaintDeviceSP KisMaskManager::activeDevice() { KisMaskSP mask = activeMask(); return mask ? mask->paintDevice() : 0; } void KisMaskManager::activateMask(KisMaskSP mask) { Q_UNUSED(mask); } void KisMaskManager::masksUpdated() { m_view->updateGUI(); } void KisMaskManager::adjustMaskPosition(KisNodeSP node, KisNodeSP activeNode, bool avoidActiveNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(node); Q_ASSERT(activeNode); if (!avoidActiveNode && activeNode->allowAsChild(node)) { parent = activeNode; above = activeNode->lastChild(); - } else if (activeNode->parent() && activeNode->parent()->allowAsChild(node)) { + } else if (activeNode->parent() && activeNode->parent()->allowAsChild(node) + && activeNode->parent()->parent() /* we don't want to add masks to root */) { parent = activeNode->parent(); above = activeNode; } else { KisNodeSP t = activeNode; while ((t = t->nextSibling())) { if (t->allowAsChild(node)) { parent = t; above = t->lastChild(); break; } } if (!t) { t = activeNode; while ((t = t->prevSibling())) { if (t->allowAsChild(node)) { parent = t; above = t->lastChild(); break; } } } if (!t && activeNode->parent()) { adjustMaskPosition(node, activeNode->parent(), true, parent, above); } else if (!t) { KisImageWSP image = m_view->image(); KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); m_commandsAdapter->addNode(layer, activeNode, 0); parent = layer; above = 0; } } } void KisMaskManager::createMaskCommon(KisMaskSP mask, KisNodeSP activeNode, KisPaintDeviceSP copyFrom, const KUndo2MagicString& macroName, const QString &nodeType, const QString &nodeName, bool suppressSelection, bool avoidActiveNode, bool updateImage) { m_commandsAdapter->beginMacro(macroName); KisNodeSP parent; KisNodeSP above; adjustMaskPosition(mask, activeNode, avoidActiveNode, parent, above); KisLayerSP parentLayer = qobject_cast(parent.data()); Q_ASSERT(parentLayer); bool shouldDeselectGlobalSelection = false; if (!suppressSelection) { if (copyFrom) { mask->initSelection(copyFrom, parentLayer); } else { mask->initSelection(m_view->selection(), parentLayer); shouldDeselectGlobalSelection = m_view->selection(); } } //counting number of KisSelectionMask QList masks = parentLayer->childNodes(QStringList(nodeType),KoProperties()); int number = masks.count() + 1; mask->setName(nodeName + QString(" ") + QString::number(number)); m_commandsAdapter->addNode(mask, parentLayer, above, updateImage, updateImage); if (shouldDeselectGlobalSelection) { m_commandsAdapter->addExtraCommand(new KisDeselectGlobalSelectionCommand(m_imageView->image())); } m_commandsAdapter->endMacro(); masksUpdated(); } KisNodeSP KisMaskManager::createSelectionMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool convertActiveNode) { if (!activeNode->isEditable()) { return 0; } KisSelectionMaskSP mask = new KisSelectionMask(m_view->image()); createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Selection Mask"), "KisSelectionMask", i18n("Selection"), false, convertActiveNode, false); mask->setActive(true); if (convertActiveNode) { m_commandsAdapter->removeNode(activeNode); } return mask; } KisNodeSP KisMaskManager::createTransparencyMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool convertActiveNode) { if (!activeNode->isEditable()) { return 0; } KisMaskSP mask = new KisTransparencyMask(); createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Transparency Mask"), "KisTransparencyMask", i18n("Transparency Mask"), false, convertActiveNode); if (convertActiveNode) { m_commandsAdapter->removeNode(activeNode); } return mask; } KisNodeSP KisMaskManager::createFilterMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool quiet, bool convertActiveNode) { if (!activeNode->isEditable()) { return 0; } KisFilterMaskSP mask = new KisFilterMask(); createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Filter Mask"), "KisFilterMask", i18n("Filter Mask"), false, convertActiveNode); if (convertActiveNode) { m_commandsAdapter->removeNode(activeNode); } /** * FIXME: We'll use layer's original for creation of a thumbnail. * Actually, we can't use it's projection as newly created mask * may be going to be inserted in the middle of the masks stack */ KisPaintDeviceSP originalDevice = mask->parent()->original(); KisDlgAdjustmentLayer dialog(mask, mask.data(), originalDevice, mask->name(), i18n("New Filter Mask"), - m_view); + m_view, qApp->activeWindow()); // If we are supposed to not disturb the user, don't start asking them about things. if(quiet) { KisFilterConfigurationSP filter = KisFilterRegistry::instance()->values().first()->defaultConfiguration(); if (filter) { mask->setFilter(filter); mask->setName(mask->name()); } return mask; } if (dialog.exec() == QDialog::Accepted) { KisFilterConfigurationSP filter = dialog.filterConfiguration(); if (filter) { QString name = dialog.layerName(); mask->setFilter(filter); mask->setName(name); } return mask; } else { m_commandsAdapter->undoLastCommand(); } return 0; } KisNodeSP KisMaskManager::createColorizeMask(KisNodeSP activeNode) { if (!activeNode->isEditable()) { return 0; } KisColorizeMaskSP mask = new KisColorizeMask(); createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Colorize Mask"), "KisColorizeMask", i18n("Colorize Mask"), true, false); mask->setImage(m_view->image()); mask->initializeCompositeOp(); delete mask->setColorSpace(mask->parent()->colorSpace()); return mask; } KisNodeSP KisMaskManager::createTransformMask(KisNodeSP activeNode) { if (!activeNode->isEditable()) { return 0; } KisTransformMaskSP mask = new KisTransformMask(); createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Transform Mask"), "KisTransformMask", i18n("Transform Mask"), true, false); return mask; } void KisMaskManager::maskProperties() { if (!activeMask()) return; if (activeMask()->inherits("KisFilterMask")) { KisFilterMask *mask = static_cast(activeMask().data()); KisLayerSP layer = qobject_cast(mask->parent().data()); if (! layer) return; KisPaintDeviceSP dev = layer->original(); if (!dev) { return; } KisDlgAdjLayerProps dlg(layer, mask, dev, m_view, mask->filter().data(), mask->name(), i18n("Filter Mask Properties"), m_view->mainWindow(), "dlgeffectmaskprops"); KisFilterConfigurationSP configBefore(mask->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); mask->setName(dlg.layerName()); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(mask, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { mask->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); mask->setDirty(); } } } else { // Not much to show for transparency or selection masks? } } diff --git a/libs/ui/kis_painting_assistant.cc b/libs/ui/kis_painting_assistant.cc index 2b8767e657..6ab2106630 100644 --- a/libs/ui/kis_painting_assistant.cc +++ b/libs/ui/kis_painting_assistant.cc @@ -1,886 +1,896 @@ /* * Copyright (c) 2008,2011 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2017 Scott Petrovic * * 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_painting_assistant.h" #include "kis_coordinates_converter.h" #include "kis_debug.h" #include "kis_dom_utils.h" #include #include "kis_tool.h" #include "kis_config.h" #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance) struct KisPaintingAssistantHandle::Private { QList assistants; char handle_type; }; KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs) : QPointF(rhs) , KisShared() , d(new Private) { dbgUI << "KisPaintingAssistantHandle ctor"; } KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt) { setX(pt.x()); setY(pt.y()); return *this; } void KisPaintingAssistantHandle::setType(char type) { d->handle_type = type; } char KisPaintingAssistantHandle::handleType() const { return d->handle_type; } KisPaintingAssistantHandle::~KisPaintingAssistantHandle() { Q_ASSERT(d->assistants.empty()); delete d; } void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant) { Q_ASSERT(!d->assistants.contains(assistant)); d->assistants.append(assistant); } void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant) { d->assistants.removeOne(assistant); Q_ASSERT(!d->assistants.contains(assistant)); } bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) const { return d->assistants.contains(assistant); } void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle) { if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) { return; } Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) { if (!assistant->handles().contains(this)) { assistant->replaceHandle(handle, this); } } } void KisPaintingAssistantHandle::uncache() { Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { assistant->uncache(); } } struct KisPaintingAssistant::Private { Private(); explicit Private(const Private &rhs); KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q); QList handles, sideHandles; KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle; // share everything except handles between the clones struct SharedData { QString id; QString name; bool isSnappingActive; bool outlineVisible; KisCanvas2* m_canvas = 0; QPixmapCache::Key cached; QRect cachedRect; // relative to boundingRect().topLeft() struct TranslationInvariantTransform { qreal m11, m12, m21, m22; TranslationInvariantTransform() { } TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { } bool operator==(const TranslationInvariantTransform& b) { return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22; } } cachedTransform; QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set bool useCustomColor = false; QColor assistantCustomColor = KisConfig(true).defaultAssistantsColor(); }; QSharedPointer s; }; KisPaintingAssistant::Private::Private() : s(new SharedData) { } KisPaintingAssistant::Private::Private(const Private &rhs) : s(rhs.s) { } KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q) { KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle); if (!mappedHandle) { if (origHandle) { dbgUI << "handle not found in the map, creating a new one..."; mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle)); dbgUI << "done"; mappedHandle->setType(origHandle->handleType()); handleMap.insert(origHandle, mappedHandle); } else { dbgUI << "orig handle is null, not doing anything"; mappedHandle = KisPaintingAssistantHandleSP(); } } if (mappedHandle) { mappedHandle->registerAssistant(q); } return mappedHandle; } bool KisPaintingAssistant::useCustomColor() { return d->s->useCustomColor; } void KisPaintingAssistant::setUseCustomColor(bool useCustomColor) { d->s->useCustomColor = useCustomColor; } void KisPaintingAssistant::setAssistantCustomColor(QColor color) { d->s->assistantCustomColor = color; } QColor KisPaintingAssistant::assistantCustomColor() { return d->s->assistantCustomColor; } void KisPaintingAssistant::setAssistantGlobalColorCache(const QColor &color) { d->s->assistantGlobalColorCache = color; } QColor KisPaintingAssistant::effectiveAssistantColor() const { return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache; } KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) { d->s->id = id; d->s->name = name; d->s->isSnappingActive = true; d->s->outlineVisible = true; } KisPaintingAssistant::KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap &handleMap) : d(new Private(*(rhs.d))) { dbgUI << "creating handles..."; Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) { d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this); } Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) { d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this); } #define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this) _REUSE_H(topLeft); _REUSE_H(bottomLeft); _REUSE_H(topRight); _REUSE_H(bottomRight); _REUSE_H(topMiddle); _REUSE_H(bottomMiddle); _REUSE_H(rightMiddle); _REUSE_H(leftMiddle); #undef _REUSE_H dbgUI << "done"; } bool KisPaintingAssistant::isSnappingActive() const { return d->s->isSnappingActive; } void KisPaintingAssistant::setSnappingActive(bool set) { d->s->isSnappingActive = set; } void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn) { QColor paintingColor = effectiveAssistantColor(); if (!isSnappingOn) { paintingColor.setAlpha(0.2 * paintingColor.alpha()); } painter.save(); QPen pen_a(paintingColor, 2); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path) { painter.save(); QPen pen_a(effectiveAssistantColor(), 1); pen_a.setStyle(Qt::SolidLine); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::initHandles(QList _handles) { Q_ASSERT(d->handles.isEmpty()); d->handles = _handles; Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) { handle->registerAssistant(this); } } KisPaintingAssistant::~KisPaintingAssistant() { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) { handle->unregisterAssistant(this); } if(!d->sideHandles.isEmpty()) { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { handle->unregisterAssistant(this); } } delete d; } const QString& KisPaintingAssistant::id() const { return d->s->id; } const QString& KisPaintingAssistant::name() const { return d->s->name; } void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with) { Q_ASSERT(d->handles.contains(_handle)); d->handles.replace(d->handles.indexOf(_handle), _with); Q_ASSERT(!d->handles.contains(_handle)); _handle->unregisterAssistant(this); _with->registerAssistant(this); } void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type) { Q_ASSERT(!d->handles.contains(handle)); if (HandleType::SIDE == type) { d->sideHandles.append(handle); } else { d->handles.append(handle); } handle->registerAssistant(this); handle.data()->setType(type); } void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { Q_UNUSED(updateRect); Q_UNUSED(previewVisible); findPerspectiveAssistantHandleLocation(); if (!useCache) { gc.save(); drawCache(gc, converter, assistantVisible); gc.restore(); return; } const QRect bound = boundingRect(); if (bound.isEmpty()) { return; } const QTransform transform = converter->documentToWidgetTransform(); const QRect widgetBound = transform.mapRect(bound); const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport()); if (paintRect.isEmpty()) return; QPixmap cached; bool found = QPixmapCache::find(d->s->cached, &cached); if (!(found && d->s->cachedTransform == transform && d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound); Q_ASSERT(!cacheRect.isEmpty()); if (cached.isNull() || cached.size() != cacheRect.size()) { cached = QPixmap(cacheRect.size()); } cached.fill(Qt::transparent); QPainter painter(&cached); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(cacheRect); drawCache(painter, converter, assistantVisible); painter.end(); d->s->cachedTransform = transform; d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft()); d->s->cached = QPixmapCache::insert(cached); } gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft())); if (canvas) { d->s->m_canvas = canvas; } } void KisPaintingAssistant::uncache() { d->s->cached = QPixmapCache::Key(); } QRect KisPaintingAssistant::boundingRect() const { QRectF r; Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) { r = r.united(QRectF(*h, QSizeF(1,1))); } return r.adjusted(-2, -2, 2, 2).toAlignedRect(); } bool KisPaintingAssistant::isAssistantComplete() const { return true; } QByteArray KisPaintingAssistant::saveXml(QMap &handleMap) { QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("assistant"); xml.writeAttribute("type",d->s->id); xml.writeAttribute("active", QString::number(d->s->isSnappingActive)); xml.writeAttribute("useCustomColor", QString::number(d->s->useCustomColor)); xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor)); saveCustomXml(&xml); // if any specific assistants have custom XML data to save to // write individual handle data xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { int id = handleMap.size(); if (!handleMap.contains(handle)){ handleMap.insert(handle, id); } id = handleMap.value(handle); xml.writeStartElement("handle"); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); return data; } void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml) { Q_UNUSED(xml); } void KisPaintingAssistant::loadXml(KoStore* store, QMap &handleMap, QString path) { int id = 0; double x = 0.0, y = 0.0; store->open(path); QByteArray data = store->read(store->size()); QXmlStreamReader xml(data); while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "assistant") { QStringRef active = xml.attributes().value("active"); setSnappingActive( (active != "0") ); // load custom shared assistant properties if ( xml.attributes().hasAttribute("useCustomColor")) { QStringRef useCustomColor = xml.attributes().value("useCustomColor"); bool usingColor = false; if (useCustomColor.toString() == "1") { usingColor = true; } setUseCustomColor(usingColor); } if ( xml.attributes().hasAttribute("customColor")) { QStringRef customColor = xml.attributes().value("customColor"); setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) ); } } loadCustomXml(&xml); if (xml.name() == "handle") { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { id = strId.toInt(); x = strX.toDouble(); y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } } addHandle(handleMap.value(id), HandleType::NORMAL); } break; default: break; } } store->close(); } bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml) { Q_UNUSED(xml); return true; } void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count) { if (d->s->id == "ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ellipse"); assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "spline"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "spline"); assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "perspective"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "perspective"); assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "vanishing point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "vanishing point"); assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "infinite ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "infinite ruler"); assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "parallel ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "parallel ruler"); assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "concentric ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "concentric ellipse"); assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "fisheye-point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "fisheye-point"); assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ruler"); assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } } void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() { QList hHandlesList; QList vHandlesList; uint vHole = 0,hHole = 0; KisPaintingAssistantHandleSP oppHandle; if (d->handles.size() == 4 && d->s->id == "perspective") { //get the handle opposite to the first handle oppHandle = oppHandleOne(); //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively. Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) { hHandlesList.append(handle); hHole = hHandlesList.size() - 1; vHandlesList.append(handle); vHole = vHandlesList.size() - 1; /* sort handles on the basis of X-coordinate */ while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) { hHandlesList.swap(hHole-1, hHole); hHole = hHole - 1; } /* sort handles on the basis of Y-coordinate */ while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) { vHandlesList.swap(vHole-1, vHole); vHole = vHole - 1; } } /* give the handles their respective positions */ if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) { d->topLeft = vHandlesList.at(1); d->topRight= vHandlesList.at(0); } else { d->topLeft = vHandlesList.at(0); d->topRight = vHandlesList.at(1); } if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) { d->bottomLeft = vHandlesList.at(3); d->bottomRight = vHandlesList.at(2); } else { d->bottomLeft= vHandlesList.at(2); d->bottomRight = vHandlesList.at(3); } /* find if the handles that should be opposite are actually oppositely positioned */ if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) || (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) {} else { if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) { d->topLeft = hHandlesList.at(1); d->bottomLeft= hHandlesList.at(0); } else { d->topLeft = hHandlesList.at(0); d->bottomLeft = hHandlesList.at(1); } if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) { d->topRight = hHandlesList.at(3); d->bottomRight = hHandlesList.at(2); } else { d->topRight= hHandlesList.at(2); d->bottomRight = hHandlesList.at(3); } } /* Setting the middle handles as needed */ if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) { d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5); d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); addHandle(d->rightMiddle.data(), HandleType::SIDE); addHandle(d->leftMiddle.data(), HandleType::SIDE); addHandle(d->bottomMiddle.data(), HandleType::SIDE); addHandle(d->topMiddle.data(), HandleType::SIDE); } else { d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); } } } KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne() { QPointF intersection(0,0); if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) { return d->handles.at(1); } else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) { return d->handles.at(2); } else { return d->handles.at(3); } } KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() { return d->topLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const { return d->topLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() { return d->bottomLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const { return d->bottomLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() { return d->topRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const { return d->topRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() { return d->bottomRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const { return d->bottomRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() { return d->topMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const { return d->topMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() { return d->bottomMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const { return d->bottomMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() { return d->rightMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const { return d->rightMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() { return d->leftMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const { return d->leftMiddle; } const QList& KisPaintingAssistant::handles() const { return d->handles; } QList KisPaintingAssistant::handles() { return d->handles; } const QList& KisPaintingAssistant::sideHandles() const { return d->sideHandles; } QList KisPaintingAssistant::sideHandles() { return d->sideHandles; } bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo) { int m_handleSize = 16; QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); return handlerect.contains(pointOne); } KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point) { if (!d->s->m_canvas) { return 0; } if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) { return topLeft(); } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) { return topRight(); } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) { return bottomLeft(); } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) { return bottomRight(); } return 0; } QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const { QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords); return d->s->m_canvas->viewConverter()->documentToView(documentCoord); } double KisPaintingAssistant::norm2(const QPointF& p) { return p.x() * p.x() + p.y() * p.y(); } +QList KisPaintingAssistant::cloneAssistantList(const QList &list) +{ + QMap handleMap; + QList clonedList; + for (auto i = list.begin(); i != list.end(); ++i) { + clonedList << (*i)->clone(handleMap); + } + return clonedList; +} + /* * KisPaintingAssistantFactory classes */ KisPaintingAssistantFactory::KisPaintingAssistantFactory() { } KisPaintingAssistantFactory::~KisPaintingAssistantFactory() { } KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry() { } KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry() { Q_FOREACH (const QString &id, keys()) { delete get(id); } dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry "; } KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance() { return s_instance; } diff --git a/libs/ui/kis_painting_assistant.h b/libs/ui/kis_painting_assistant.h index 6f2150bf93..741194f449 100644 --- a/libs/ui/kis_painting_assistant.h +++ b/libs/ui/kis_painting_assistant.h @@ -1,237 +1,242 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2017 Scott Petrovic * * 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_PAINTING_ASSISTANT_H_ #define _KIS_PAINTING_ASSISTANT_H_ #include #include #include #include #include #include #include #include #include #include #include class QPainter; class QRect; class QRectF; class KoStore; class KisCoordinatesConverter; class KisCanvas2; class QDomDocument; class QDomElement; #include #include class KisPaintingAssistantHandle; typedef KisSharedPtr KisPaintingAssistantHandleSP; class KisPaintingAssistant; class QPainterPath; enum HandleType { NORMAL, SIDE, CORNER, VANISHING_POINT, ANCHOR }; /** * Represent an handle of the assistant, used to edit the parameters * of an assistants. Handles can be shared between assistants. */ class KRITAUI_EXPORT KisPaintingAssistantHandle : public QPointF, public KisShared { friend class KisPaintingAssistant; public: KisPaintingAssistantHandle(double x, double y); explicit KisPaintingAssistantHandle(QPointF p); KisPaintingAssistantHandle(const KisPaintingAssistantHandle&); ~KisPaintingAssistantHandle(); void mergeWith(KisPaintingAssistantHandleSP); void uncache(); KisPaintingAssistantHandle& operator=(const QPointF&); void setType(char type); char handleType() const; private: void registerAssistant(KisPaintingAssistant*); void unregisterAssistant(KisPaintingAssistant*); bool containsAssistant(KisPaintingAssistant*) const; private: struct Private; Private* const d; }; /** * A KisPaintingAssistant is an object that assist the drawing on the canvas. * With this class you can implement virtual equivalent to ruler or compas. */ class KRITAUI_EXPORT KisPaintingAssistant { public: KisPaintingAssistant(const QString& id, const QString& name); virtual ~KisPaintingAssistant(); virtual KisPaintingAssistantSP clone(QMap &handleMap) const = 0; const QString& id() const; const QString& name() const; bool isSnappingActive() const; void setSnappingActive(bool set); /** * Adjust the position given in parameter. * @param point the coordinates in point in the document reference * @param strokeBegin the coordinates of the beginning of the stroke */ virtual QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) = 0; virtual void endStroke() { } virtual QPointF buttonPosition() const = 0; virtual int numHandles() const = 0; void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with); void addHandle(KisPaintingAssistantHandleSP handle, HandleType type); QColor effectiveAssistantColor() const; /// should this assistant use a custom color for the display? global color will be used if this is false bool useCustomColor(); void setUseCustomColor(bool useCustomColor); /// getter and setter for assistant's custom color void setAssistantCustomColor(QColor color); QColor assistantCustomColor(); void setAssistantGlobalColorCache(const QColor &color); virtual void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, bool cached = true,KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true); void uncache(); const QList& handles() const; QList handles(); const QList& sideHandles() const; QList sideHandles(); QByteArray saveXml( QMap &handleMap); virtual void saveCustomXml(QXmlStreamWriter* xml); //in case specific assistants have custom properties (like vanishing point) void loadXml(KoStore *store, QMap &handleMap, QString path); virtual bool loadCustomXml(QXmlStreamReader* xml); void saveXmlList(QDomDocument& doc, QDomElement& ssistantsElement, int count); void findPerspectiveAssistantHandleLocation(); KisPaintingAssistantHandleSP oppHandleOne(); /** * Get the topLeft, bottomLeft, topRight and BottomRight corners of the assistant * Some assistants like the perspective grid have custom logic built around certain handles */ const KisPaintingAssistantHandleSP topLeft() const; KisPaintingAssistantHandleSP topLeft(); const KisPaintingAssistantHandleSP topRight() const; KisPaintingAssistantHandleSP topRight(); const KisPaintingAssistantHandleSP bottomLeft() const; KisPaintingAssistantHandleSP bottomLeft(); const KisPaintingAssistantHandleSP bottomRight() const; KisPaintingAssistantHandleSP bottomRight(); const KisPaintingAssistantHandleSP topMiddle() const; KisPaintingAssistantHandleSP topMiddle(); const KisPaintingAssistantHandleSP rightMiddle() const; KisPaintingAssistantHandleSP rightMiddle(); const KisPaintingAssistantHandleSP leftMiddle() const; KisPaintingAssistantHandleSP leftMiddle(); const KisPaintingAssistantHandleSP bottomMiddle() const; KisPaintingAssistantHandleSP bottomMiddle(); // calculates whether a point is near one of the corner points of the assistant // returns: a corner point from the perspective assistant if the given node is close // only called once in code when calculating the perspective assistant KisPaintingAssistantHandleSP closestCornerHandleFromPoint(QPointF point); // determines if two points are close to each other // only used by the nodeNearPoint function (perspective grid assistant). bool areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo); /// determines if the assistant has enough handles to be considered created /// new assistants get in a "creation" phase where they are currently being made on the canvas /// it will return false if we are in the middle of creating the assistant. virtual bool isAssistantComplete() const; public: /** * This will render the final output. The drawCache does rendering most of the time so be sure to check that */ void drawPath(QPainter& painter, const QPainterPath& path, bool drawActive=true); void drawPreview(QPainter& painter, const QPainterPath& path); static double norm2(const QPointF& p); protected: explicit KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap &handleMap); virtual QRect boundingRect() const; /// performance layer where the graphics can be drawn from a cache instead of generated every render update virtual void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) = 0; void initHandles(QList _handles); QList m_handles; QPointF pixelToView(const QPoint pixelCoords) const; +public: + /// clones the list of assistants + /// the originally shared handles will still be shared + /// the cloned assistants do not share any handle with the original assistants + static QList cloneAssistantList(const QList &list); private: struct Private; Private* const d; }; /** * Allow to create a painting assistant. */ class KRITAUI_EXPORT KisPaintingAssistantFactory { public: KisPaintingAssistantFactory(); virtual ~KisPaintingAssistantFactory(); virtual QString id() const = 0; virtual QString name() const = 0; virtual KisPaintingAssistant* createPaintingAssistant() const = 0; }; class KRITAUI_EXPORT KisPaintingAssistantFactoryRegistry : public KoGenericRegistry { public: KisPaintingAssistantFactoryRegistry(); ~KisPaintingAssistantFactoryRegistry() override; static KisPaintingAssistantFactoryRegistry* instance(); }; #endif diff --git a/libs/ui/kis_safe_document_loader.cpp b/libs/ui/kis_safe_document_loader.cpp index 2b240ccf8a..766f116e16 100644 --- a/libs/ui/kis_safe_document_loader.cpp +++ b/libs/ui/kis_safe_document_loader.cpp @@ -1,272 +1,274 @@ /* * 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 "kis_safe_document_loader.h" #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include #include "kis_signal_compressor.h" #include "KisPart.h" class FileSystemWatcherWrapper : public QObject { Q_OBJECT public: FileSystemWatcherWrapper() { connect(&m_watcher, SIGNAL(fileChanged(QString)), SIGNAL(fileChanged(QString))); connect(&m_watcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged(QString))); } bool addPath(const QString &file) { bool result = true; const QString ufile = unifyFilePath(file); if (m_pathCount.contains(ufile)) { m_pathCount[ufile]++; } else { m_pathCount.insert(ufile, 1); result = m_watcher.addPath(ufile); } return result; } bool removePath(const QString &file) { bool result = true; const QString ufile = unifyFilePath(file); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_pathCount.contains(ufile), false); if (m_pathCount[ufile] == 1) { m_pathCount.remove(ufile); result = m_watcher.removePath(ufile); } else { m_pathCount[ufile]--; } return result; } QStringList files() const { return m_watcher.files(); } private Q_SLOTS: void slotFileChanged(const QString &path) { // re-add the file after QSaveFile optimization if (!m_watcher.files().contains(path) && QFileInfo(path).exists()) { m_watcher.addPath(path); } } Q_SIGNALS: void fileChanged(const QString &path); private: QString unifyFilePath(const QString &path) { return QFileInfo(path).absoluteFilePath(); } private: QFileSystemWatcher m_watcher; QHash m_pathCount; }; Q_GLOBAL_STATIC(FileSystemWatcherWrapper, s_fileSystemWatcher) struct KisSafeDocumentLoader::Private { Private() : fileChangedSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE) { } QScopedPointer doc; KisSignalCompressor fileChangedSignalCompressor; - QTimer delayedLoadTimer; bool isLoading = false; bool fileChangedFlag = false; QString path; QString temporaryPath; qint64 initialFileSize = 0; QDateTime initialFileTimeStamp; }; KisSafeDocumentLoader::KisSafeDocumentLoader(const QString &path, QObject *parent) : QObject(parent), m_d(new Private()) { connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); connect(&m_d->fileChangedSignalCompressor, SIGNAL(timeout()), SLOT(fileChangedCompressed())); - connect(&m_d->delayedLoadTimer, SIGNAL(timeout()), - SLOT(delayedLoadStart())); - - m_d->delayedLoadTimer.setSingleShot(true); - m_d->delayedLoadTimer.setInterval(100 /* ms */); - setPath(path); } KisSafeDocumentLoader::~KisSafeDocumentLoader() { if (!m_d->path.isEmpty()) { s_fileSystemWatcher->removePath(m_d->path); } delete m_d; } void KisSafeDocumentLoader::setPath(const QString &path) { if (path.isEmpty()) return; if (!m_d->path.isEmpty()) { s_fileSystemWatcher->removePath(m_d->path); } m_d->path = path; s_fileSystemWatcher->addPath(m_d->path); } void KisSafeDocumentLoader::reloadImage() { fileChangedCompressed(true); } void KisSafeDocumentLoader::fileChanged(QString path) { if (path == m_d->path) { if (s_fileSystemWatcher->files().contains(path) == false && QFileInfo(path).exists()) { //When a path is renamed it is removed, so we ought to readd it. s_fileSystemWatcher->addPath(path); } m_d->fileChangedFlag = true; m_d->fileChangedSignalCompressor.start(); } } void KisSafeDocumentLoader::fileChangedCompressed(bool sync) { if (m_d->isLoading) return; QFileInfo initialFileInfo(m_d->path); m_d->initialFileSize = initialFileInfo.size(); m_d->initialFileTimeStamp = initialFileInfo.lastModified(); // it may happen when the file is flushed by // so other application if (!m_d->initialFileSize) return; m_d->isLoading = true; m_d->fileChangedFlag = false; m_d->temporaryPath = QDir::tempPath() + QDir::separator() + QString("krita_file_layer_copy_%1_%2.%3") .arg(QApplication::applicationPid()) .arg(qrand()) .arg(initialFileInfo.suffix()); QFile::copy(m_d->path, m_d->temporaryPath); if (!sync) { - m_d->delayedLoadTimer.start(); + QTimer::singleShot(100, this, SLOT(delayedLoadStart())); } else { QApplication::processEvents(); delayedLoadStart(); } } void KisSafeDocumentLoader::delayedLoadStart() { QFileInfo originalInfo(m_d->path); QFileInfo tempInfo(m_d->temporaryPath); bool successfullyLoaded = false; if (!m_d->fileChangedFlag && originalInfo.size() == m_d->initialFileSize && originalInfo.lastModified() == m_d->initialFileTimeStamp && tempInfo.size() == m_d->initialFileSize) { m_d->doc.reset(KisPart::instance()->createDocument()); if (m_d->path.toLower().endsWith("ora") || m_d->path.toLower().endsWith("kra")) { QScopedPointer store(KoStore::createStore(m_d->temporaryPath, KoStore::Read)); - if (store) { + if (store && !store->bad()) { if (store->open(QString("mergedimage.png"))) { QByteArray bytes = store->read(store->size()); store->close(); QImage mergedImage; mergedImage.loadFromData(bytes); + Q_ASSERT(!mergedImage.isNull()); KisImageSP image = new KisImage(0, mergedImage.width(), mergedImage.height(), KoColorSpaceRegistry::instance()->rgb8(), ""); KisPaintLayerSP layer = new KisPaintLayer(image, "", OPACITY_OPAQUE_U8); layer->paintDevice()->convertFromQImage(mergedImage, 0); image->addNode(layer, image->rootLayer()); + image->initialRefreshGraph(); m_d->doc->setCurrentImage(image); + successfullyLoaded = true; } + else { + qWarning() << "delayedLoadStart: Could not open mergedimage.png"; + } + } + else { + qWarning() << "delayedLoadStart: Store was bad"; } } else { successfullyLoaded = m_d->doc->openUrl(QUrl::fromLocalFile(m_d->temporaryPath), KisDocument::DontAddToRecent); } } else { dbgKrita << "File was modified externally. Restarting."; dbgKrita << ppVar(m_d->fileChangedFlag); dbgKrita << ppVar(m_d->initialFileSize); dbgKrita << ppVar(m_d->initialFileTimeStamp); dbgKrita << ppVar(originalInfo.size()); dbgKrita << ppVar(originalInfo.lastModified()); dbgKrita << ppVar(tempInfo.size()); } QFile::remove(m_d->temporaryPath); m_d->isLoading = false; if (!successfullyLoaded) { // Restart the attempt m_d->fileChangedSignalCompressor.start(); } else { KisPaintDeviceSP paintDevice = new KisPaintDevice(m_d->doc->image()->colorSpace()); KisPaintDeviceSP projection = m_d->doc->image()->projection(); paintDevice->makeCloneFrom(projection, projection->extent()); emit loadingFinished(paintDevice, m_d->doc->image()->xRes(), m_d->doc->image()->yRes()); } m_d->doc.reset(); } #include "kis_safe_document_loader.moc" diff --git a/libs/ui/opengl/KisScreenInformationAdapter.cpp b/libs/ui/opengl/KisScreenInformationAdapter.cpp index 236d42bb92..e4c3c075c9 100644 --- a/libs/ui/opengl/KisScreenInformationAdapter.cpp +++ b/libs/ui/opengl/KisScreenInformationAdapter.cpp @@ -1,297 +1,299 @@ /* * 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 "KisScreenInformationAdapter.h" #include "kis_debug.h" #include #include #include #include #ifdef Q_OS_WIN #include #include #include #include #include "EGL/egl.h" #include "EGL/eglext.h" #endif namespace { struct EGLException { EGLException() {} EGLException(const QString &what) : m_what(what) {} QString what() const { return m_what; } private: QString m_what; }; template void getProcAddressSafe(QOpenGLContext *context, const char *funcName, FuncType &func) { func = reinterpret_cast(context->getProcAddress(funcName)); if (!func) { throw EGLException(QString("failed to fetch function %1").arg(funcName)); } } #ifdef Q_OS_WIN typedef const char *(EGLAPIENTRYP PFNEGLQUERYSTRINGPROC) (EGLDisplay dpy, EGLint name); #endif } struct KisScreenInformationAdapter::Private { void initialize(QOpenGLContext *context); QOpenGLContext *context; QString errorString; #ifdef Q_OS_WIN Microsoft::WRL::ComPtr dxgiAdapter; #endif }; KisScreenInformationAdapter::KisScreenInformationAdapter(QOpenGLContext *context) : m_d(new Private) { - m_d->initialize(context); + if (context) { + m_d->initialize(context); + } } KisScreenInformationAdapter::~KisScreenInformationAdapter() { } void KisScreenInformationAdapter::Private::initialize(QOpenGLContext *newContext) { context = newContext; errorString.clear(); try { #if defined Q_OS_WIN && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!context->isOpenGLES()) { throw EGLException("the context is not OpenGL ES"); } PFNEGLQUERYSTRINGPROC queryString = nullptr; getProcAddressSafe(context, "eglQueryString", queryString); const char* client_extensions = queryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const QList extensions = QByteArray(client_extensions).split(' '); if (!extensions.contains("EGL_ANGLE_platform_angle_d3d") || !extensions.contains("EGL_ANGLE_device_creation_d3d11")) { throw EGLException("the context is not Angle + D3D11"); } PFNEGLQUERYDISPLAYATTRIBEXTPROC queryDisplayAttribEXT = nullptr; PFNEGLQUERYDEVICEATTRIBEXTPROC queryDeviceAttribEXT = nullptr; getProcAddressSafe(context, "eglQueryDisplayAttribEXT", queryDisplayAttribEXT); getProcAddressSafe(context, "eglQueryDeviceAttribEXT", queryDeviceAttribEXT); QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface(); EGLDisplay display = reinterpret_cast(nativeInterface->nativeResourceForContext("egldisplay", context)); if (!display) { throw EGLException( QString("couldn't request EGLDisplay handle, display = 0x%1").arg(uintptr_t(display), 0, 16)); } EGLAttrib value = 0; EGLBoolean result = false; result = queryDisplayAttribEXT(display, EGL_DEVICE_EXT, &value); if (!result || value == EGL_NONE) { throw EGLException( QString("couldn't request EGLDeviceEXT handle, result = 0x%1, value = 0x%2") .arg(result, 0, 16).arg(value, 0, 16)); } EGLDeviceEXT device = reinterpret_cast(value); result = queryDeviceAttribEXT(device, EGL_D3D11_DEVICE_ANGLE, &value); if (!result || value == EGL_NONE) { throw EGLException( QString("couldn't request ID3D11Device pointer, result = 0x%1, value = 0x%2") .arg(result, 0, 16).arg(value, 0, 16)); } ID3D11Device *deviceD3D = reinterpret_cast(value); { HRESULT result = 0; Microsoft::WRL::ComPtr dxgiDevice; result = deviceD3D->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice); if (FAILED(result)) { throw EGLException( QString("couldn't request IDXGIDevice pointer, result = 0x%1").arg(result, 0, 16)); } Microsoft::WRL::ComPtr dxgiAdapter; result = dxgiDevice->GetParent(__uuidof(IDXGIAdapter1), (void**)&dxgiAdapter); if (FAILED(result)) { throw EGLException( QString("couldn't request IDXGIAdapter1 pointer, result = 0x%1").arg(result, 0, 16)); } this->dxgiAdapter = dxgiAdapter; } #else throw EGLException("current platform doesn't support fetching display information"); #endif } catch (EGLException &e) { this->context = 0; this->errorString = e.what(); #ifdef Q_OS_WIN this->dxgiAdapter.Reset(); #endif } } bool KisScreenInformationAdapter::isValid() const { #ifdef Q_OS_WIN return m_d->context && m_d->dxgiAdapter; #else return false; #endif } QString KisScreenInformationAdapter::errorString() const { return m_d->errorString; } KisScreenInformationAdapter::ScreenInfo KisScreenInformationAdapter::infoForScreen(QScreen *screen) const { ScreenInfo info; #if defined Q_OS_WIN && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface(); HMONITOR monitor = reinterpret_cast(nativeInterface->nativeResourceForScreen("handle", screen)); if (!monitor) { qWarning("%s: failed to get HMONITOR handle for screen: screen = 0x%X, monitor = 0x%X", __PRETTY_FUNCTION__, screen, monitor); } UINT i = 0; Microsoft::WRL::ComPtr currentOutput; while (m_d->dxgiAdapter->EnumOutputs(i, ¤tOutput) != DXGI_ERROR_NOT_FOUND) { HRESULT result = 0; Microsoft::WRL::ComPtr output6; result = currentOutput.As(&output6); if (output6) { DXGI_OUTPUT_DESC1 desc; result = output6->GetDesc1(&desc); if (desc.Monitor == monitor) { info.screen = screen; info.bitsPerColor = desc.BitsPerColor; info.redPrimary[0] = desc.RedPrimary[0]; info.redPrimary[1] = desc.RedPrimary[1]; info.greenPrimary[0] = desc.GreenPrimary[0]; info.greenPrimary[1] = desc.GreenPrimary[1]; info.bluePrimary[0] = desc.BluePrimary[0]; info.bluePrimary[1] = desc.BluePrimary[1]; info.whitePoint[0] = desc.WhitePoint[0]; info.whitePoint[1] = desc.WhitePoint[1]; info.minLuminance = desc.MinLuminance; info.maxLuminance = desc.MaxLuminance; info.maxFullFrameLuminance = desc.MaxFullFrameLuminance; info.colorSpace = KisSurfaceColorSpace::DefaultColorSpace; if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) { info.colorSpace = KisSurfaceColorSpace::sRGBColorSpace; } else if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) { #ifdef HAVE_HDR info.colorSpace = KisSurfaceColorSpace::scRGBColorSpace; #else qWarning("WARNING: scRGB display color space is not supported by Qt's build"); #endif } else if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { #ifdef HAVE_HDR info.colorSpace = KisSurfaceColorSpace::bt2020PQColorSpace; #else qWarning("WARNING: bt2020-pq display color space is not supported by Qt's build"); #endif } else { qWarning("WARNING: unknown display color space! 0x%X", desc.ColorSpace); } break; } } i++; } #endif Q_UNUSED(screen); return info; } QDebug operator<<(QDebug dbg, const KisScreenInformationAdapter::ScreenInfo &info) { QDebugStateSaver saver(dbg); if (info.isValid()) { dbg.nospace() << "ScreenInfo(" << "screen " << info.screen << ", bitsPerColor " << info.bitsPerColor << ", colorSpace " << info.colorSpace << ", redPrimary " << "(" << info.redPrimary[0] << ", " << info.redPrimary[1] << ")" << ", greenPrimary " << "(" << info.greenPrimary[0] << ", " << info.greenPrimary[1] << ")" << ", bluePrimary " << "(" << info.bluePrimary[0] << ", " << info.bluePrimary[1] << ")" << ", whitePoint " << "(" << info.whitePoint[0] << ", " << info.whitePoint[1] << ")" << ", minLuminance " << info.minLuminance << ", maxLuminance " << info.maxLuminance << ", maxFullFrameLuminance " << info.maxFullFrameLuminance << ')'; } else { dbg.nospace() << "ScreenInfo()"; } return dbg; } diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index cfa4b9c64c..a800340260 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,182 +1,183 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp kis_file_layer_test.cpp kis_multinode_property_test.cpp KisFrameSerializerTest.cpp KisFrameCacheStoreTest.cpp kis_animation_exporter_test.cpp kis_prescaled_projection_test.cpp kis_asl_layer_style_serializer_test.cpp kis_animation_importer_test.cpp KisSpinBoxSplineUnitConverterTest.cpp + KisDocumentReplaceTest.cpp LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-" ) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME KisSelectionDecorationTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeDummiesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeShapesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisModelIndexConverterTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME KisCategorizedListModelTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeJugglerCompressedTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisInputManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME kis_shape_controller_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME KisExiv2Test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME KisClipboardTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeBenchmark LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME KisPaintOnTransparencyMaskTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FillProcessingVisitorTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME FilterStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME KisSelectionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME KisNodeManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisDummiesFacadeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisZoomAndPanTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME KisActionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME KisCategoriesMapperTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp TEST_NAME kis_animation_frame_cache_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_derived_resources_test.cpp TEST_NAME kis_derived_resources_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") diff --git a/libs/ui/tests/KisDocumentReplaceTest.cpp b/libs/ui/tests/KisDocumentReplaceTest.cpp new file mode 100644 index 0000000000..c3ef8e9ae6 --- /dev/null +++ b/libs/ui/tests/KisDocumentReplaceTest.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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 "KisDocumentReplaceTest.h" + +#include +#include +#include +#include +#include +#include + +#include + +void KisDocumentReplaceTest::init() +{ + m_doc = KisPart::instance()->createDocument(); + qDebug() << m_doc->newImage("test", 512, 512, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", 0), KoColor(), KisConfig::RASTER_LAYER, 1, "", 96); +} + +void KisDocumentReplaceTest::finalize() +{ + delete m_doc; + m_doc = 0; +} + +void KisDocumentReplaceTest::testCopyFromDocument() +{ + init(); + QScopedPointer clonedDoc(m_doc->lockAndCreateSnapshot()); + KisDocument *anotherDoc = KisPart::instance()->createDocument(); + anotherDoc->newImage("test", 512, 512, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", 0), KoColor(), KisConfig::RASTER_LAYER, 2, "", 96); + KisImageSP anotherImage(anotherDoc->image()); + KisNodeSP root(anotherImage->rootLayer()); + anotherDoc->copyFromDocument(*(clonedDoc.data())); + // image pointer should not change + QCOMPARE(anotherImage.data(), anotherDoc->image().data()); + // root node should change + QVERIFY(root.data() != anotherDoc->image()->rootLayer().data()); + // node count should be the same + QList oldNodes, newNodes; + KisLayerUtils::recursiveApplyNodes(clonedDoc->image()->root(), [&oldNodes](KisNodeSP node) { oldNodes << node; }); + KisLayerUtils::recursiveApplyNodes(anotherDoc->image()->root(), [&newNodes](KisNodeSP node) { newNodes << node; }); + QCOMPARE(oldNodes.size(), newNodes.size()); + + KisPart::instance()->removeDocument(anotherDoc); + finalize(); +} + +QTEST_MAIN(KisDocumentReplaceTest) diff --git a/plugins/impex/tiff/tests/kis_tiff_test.h b/libs/ui/tests/KisDocumentReplaceTest.h similarity index 68% copy from plugins/impex/tiff/tests/kis_tiff_test.h copy to libs/ui/tests/KisDocumentReplaceTest.h index 11505376f7..002a7c6ced 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.h +++ b/libs/ui/tests/KisDocumentReplaceTest.h @@ -1,36 +1,40 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (c) 2015 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_TIFF_TEST_H_ -#define _KIS_TIFF_TEST_H_ +#ifndef KIS_DOCUMENT_REPLACE_TEST_H_ +#define KIS_DOCUMENT_REPLACE_TEST_H_ #include -class KisTiffTest : public QObject +class KisDocument; +class KisImage; + +class KisDocumentReplaceTest : public QObject { Q_OBJECT + void init(); + void finalize(); + private Q_SLOTS: - void testFiles(); - void testRoundTripRGBF16(); + void testCopyFromDocument(); - void testImportFromWriteonly(); - void testExportToReadonly(); - void testImportIncorrectFormat(); +private: + KisDocument *m_doc; }; -#endif +#endif // KIS_DOCUMENT_REPLACE_TEST_H_ diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp index 8a80fb24d2..5cac907762 100644 --- a/libs/ui/tool/kis_selection_tool_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_helper.cpp @@ -1,367 +1,370 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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_selection_tool_helper.h" #include #include #include #include #include "kis_pixel_selection.h" #include "kis_shape_selection.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_transaction.h" #include "commands/kis_selection_commands.h" #include "kis_shape_controller.h" #include #include "kis_processing_applicator.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_gui_context_command.h" #include "kis_command_utils.h" #include "commands/kis_deselect_global_selection_command.h" #include "kis_algebra_2d.h" #include "kis_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name) : m_canvas(canvas) , m_name(name) { m_image = m_canvas->viewManager()->image(); } KisSelectionToolHelper::~KisSelectionToolHelper() { } struct LazyInitGlobalSelection : public KisTransactionBasedCommand { LazyInitGlobalSelection(KisView *view) : m_view(view) {} KisView *m_view; KUndo2Command* paint() override { return !m_view->selection() ? new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0; } }; void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action) { KisView* view = m_canvas->imageView(); if (selection->selectedExactRect().isEmpty()) { m_canvas->viewManager()->selectionManager()->deselect(); return; } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ApplyToPixelSelection : public KisTransactionBasedCommand { ApplyToPixelSelection(KisView *view, KisPixelSelectionSP selection, SelectionAction action) : m_view(view), m_selection(selection), m_action(action) {} KisView *m_view; KisPixelSelectionSP m_selection; SelectionAction m_action; KUndo2Command* paint() override { KisSelectionSP selection = m_view->selection(); KIS_SAFE_ASSERT_RECOVER(selection) { return 0; } KisPixelSelectionSP pixelSelection = selection->pixelSelection(); KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; } bool hasSelection = !pixelSelection->isEmpty(); KisSelectionTransaction transaction(pixelSelection); if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) { m_action = SELECTION_REPLACE; } if (!hasSelection && m_action == SELECTION_SUBTRACT) { pixelSelection->invert(); } pixelSelection->applySelection(m_selection, m_action); QRect dirtyRect = m_view->image()->bounds(); if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_INTERSECT && m_action != SELECTION_SYMMETRICDIFFERENCE) { dirtyRect = m_selection->selectedRect(); } m_view->selection()->updateProjection(dirtyRect); KUndo2Command *savedCommand = transaction.endAndTake(); pixelSelection->setDirty(dirtyRect); if (m_view->selection()->selectedExactRect().isEmpty()) { KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); cmd->addCommand(savedCommand); cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image())); savedCommand = cmd; } return savedCommand; } }; applicator.applyCommand(new ApplyToPixelSelection(view, selection, action)); applicator.end(); } void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action) { QList shapes; shapes.append(shape); addSelectionShapes(shapes, action); } void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action) { KisView *view = m_canvas->imageView(); if (view->image()->wrapAroundModePermitted()) { view->showFloatingMessage( i18n("Shape selection does not fully " "support wraparound mode. Please " "use pixel selection instead"), KisIconUtils::loadIcon("selection-info")); } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ClearPixelSelection : public KisTransactionBasedCommand { ClearPixelSelection(KisView *view) : m_view(view) {} KisView *m_view; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } KisSelectionTransaction transaction(pixelSelection); pixelSelection->clear(); return transaction.endAndTake(); } }; if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) { applicator.applyCommand(new ClearPixelSelection(view)); } struct AddSelectionShape : public KisTransactionBasedCommand { AddSelectionShape(KisView *view, KoShape* shape, SelectionAction action) : m_view(view), m_shape(shape), m_action(action) {} KisView *m_view; KoShape* m_shape; SelectionAction m_action; KUndo2Command* paint() override { KUndo2Command *resultCommand = 0; KisSelectionSP selection = m_view->selection(); if (selection) { KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); if (shapeSelection) { QList existingShapes = shapeSelection->shapes(); if (existingShapes.size() == 1) { KoShape *currentShape = existingShapes.first(); QPainterPath path1 = currentShape->absoluteTransformation(0).map(currentShape->outline()); QPainterPath path2 = m_shape->absoluteTransformation(0).map(m_shape->outline()); QPainterPath path = path2; switch (m_action) { case SELECTION_DEFAULT: case SELECTION_REPLACE: path = path2; break; case SELECTION_INTERSECT: path = path1 & path2; break; case SELECTION_ADD: path = path1 | path2; break; case SELECTION_SUBTRACT: path = path1 - path2; break; case SELECTION_SYMMETRICDIFFERENCE: path = (path1 | path2) - (path1 & path2); break; } KoShape *newShape = KoPathShape::createShapeFromPainterPath(path); newShape->setUserData(new KisShapeSelectionMarker); KUndo2Command *parentCommand = new KUndo2Command(); m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand); m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand); resultCommand = parentCommand; } } } if (!resultCommand) { /** * Mark a shape that it belongs to a shape selection */ if(!m_shape->userData()) { m_shape->setUserData(new KisShapeSelectionMarker); } resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0); } return resultCommand; } }; Q_FOREACH (KoShape* shape, shapes) { applicator.applyCommand( new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view)); } applicator.end(); } bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action) { return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE); } bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action) { return rect.isEmpty() && action == SELECTION_ADD; } bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action) { bool result = false; if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() && (action == SELECTION_INTERSECT || action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) { // Queueing this action to ensure we avoid a race condition when unlocking the node system QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect())); result = true; } return result; } QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas) { QMenu *m_contextMenu = new QMenu(); KActionCollection *actionCollection = canvas->viewManager()->actionCollection(); + m_contextMenu->addSection(i18n("Selection Actions")); + m_contextMenu->addSeparator(); + m_contextMenu->addAction(actionCollection->action("deselect")); m_contextMenu->addAction(actionCollection->action("invert")); m_contextMenu->addAction(actionCollection->action("select_all")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer")); m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer")); m_contextMenu->addSeparator(); KisSelectionSP selection = canvas->viewManager()->selection(); if (selection && canvas->viewManager()->selectionEditable()) { m_contextMenu->addAction(actionCollection->action("edit_selection")); if (!selection->hasShapeSelection()) { m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection")); } else { m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection")); } QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform")); transformMenu->addAction(actionCollection->action("KisToolTransform")); transformMenu->addAction(actionCollection->action("selectionscale")); transformMenu->addAction(actionCollection->action("growselection")); transformMenu->addAction(actionCollection->action("shrinkselection")); transformMenu->addAction(actionCollection->action("borderselection")); transformMenu->addAction(actionCollection->action("smoothselection")); transformMenu->addAction(actionCollection->action("featherselection")); transformMenu->addAction(actionCollection->action("stroke_selection")); m_contextMenu->addSeparator(); } m_contextMenu->addAction(actionCollection->action("resizeimagetoselection")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionCollection->action("toggle_display_selection")); m_contextMenu->addAction(actionCollection->action("show-global-selection-mask")); return m_contextMenu; } SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const { if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) { if (activeSelection) { currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION; } } return currentMode; } diff --git a/libs/ui/tool/kis_tool_select_base.h b/libs/ui/tool/kis_tool_select_base.h index d3dfcc812c..b2f3a0d78b 100644 --- a/libs/ui/tool/kis_tool_select_base.h +++ b/libs/ui/tool/kis_tool_select_base.h @@ -1,407 +1,418 @@ /* This file is part of the KDE project * Copyright (C) 2009 Boudewijn Rempt * Copyright (C) 2015 Michael Abrahams * * 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. */ #ifndef KISTOOLSELECTBASE_H #define KISTOOLSELECTBASE_H #include "KoPointerEvent.h" #include "kis_tool.h" #include "kis_canvas2.h" #include "kis_selection.h" #include "kis_selection_options.h" #include "kis_selection_tool_config_widget_helper.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_selection_modifier_mapper.h" #include "strokes/move_stroke_strategy.h" #include "kis_image.h" #include "kis_cursor.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_signal_auto_connection.h" #include "kis_selection_tool_helper.h" /** * This is a basic template to create selection tools from basic path based drawing tools. * The template overrides the ability to execute alternate actions correctly. * The default behavior for the modifier keys is as follows: * * Shift: add to selection * Alt: subtract from selection * Shift+Alt: intersect current selection * Ctrl: replace selection * * The mapping itself is done in KisSelectionModifierMapper. * * Certain tools also use modifier keys to alter their behavior, e.g. forcing square proportions with the rectangle tool. * The template enables the following rules for forwarding keys: * 1) Any modifier keys held *when the tool is first activated* will determine * the new selection method. This is recorded in m_selectionActionAlternate. A * value of m_selectionActionAlternate = SELECTION_DEFAULT means no modifier was * being pressed when the tool was activated. * * 2) If the underlying tool *does not take modifier keys*, pressing modifier * keys in the middle of a stroke will change the selection method. This is * recorded in m_selectionAction. A value of SELECTION_DEFAULT means no modifier * is being pressed. Applies to the lasso tool and polygon tool. * * 3) If the underlying tool *takes modifier keys,* they will always be * forwarded to the underlying tool, and it is not possible to change the * selection method in the middle of a stroke. */ template class KisToolSelectBase : public BaseClass { public: KisToolSelectBase(KoCanvasBase* canvas, const QString toolName) : BaseClass(canvas) , m_widgetHelper(toolName) , m_selectionActionAlternate(SELECTION_DEFAULT) { KisSelectionModifierMapper::instance(); } KisToolSelectBase(KoCanvasBase* canvas, const QCursor cursor, const QString toolName) : BaseClass(canvas, cursor) , m_widgetHelper(toolName) , m_selectionActionAlternate(SELECTION_DEFAULT) { KisSelectionModifierMapper::instance(); } KisToolSelectBase(KoCanvasBase* canvas, QCursor cursor, QString toolName, KoToolBase *delegateTool) : BaseClass(canvas, cursor, delegateTool) , m_widgetHelper(toolName) , m_selectionActionAlternate(SELECTION_DEFAULT) { KisSelectionModifierMapper::instance(); } void updateActionShortcutToolTips() { KisSelectionOptions *widget = m_widgetHelper.optionWidget(); if (widget) { widget->updateActionButtonToolTip( SELECTION_REPLACE, this->action("selection_tool_mode_replace")->shortcut()); widget->updateActionButtonToolTip( SELECTION_ADD, this->action("selection_tool_mode_add")->shortcut()); widget->updateActionButtonToolTip( SELECTION_SUBTRACT, this->action("selection_tool_mode_subtract")->shortcut()); widget->updateActionButtonToolTip( SELECTION_INTERSECT, this->action("selection_tool_mode_intersect")->shortcut()); } } void activate(KoToolBase::ToolActivation activation, const QSet &shapes) { BaseClass::activate(activation, shapes); m_modeConnections.addUniqueConnection( this->action("selection_tool_mode_replace"), SIGNAL(triggered()), &m_widgetHelper, SLOT(slotReplaceModeRequested())); m_modeConnections.addUniqueConnection( this->action("selection_tool_mode_add"), SIGNAL(triggered()), &m_widgetHelper, SLOT(slotAddModeRequested())); m_modeConnections.addUniqueConnection( this->action("selection_tool_mode_subtract"), SIGNAL(triggered()), &m_widgetHelper, SLOT(slotSubtractModeRequested())); m_modeConnections.addUniqueConnection( this->action("selection_tool_mode_intersect"), SIGNAL(triggered()), &m_widgetHelper, SLOT(slotIntersectModeRequested())); updateActionShortcutToolTips(); + + if (isPixelOnly() && m_widgetHelper.optionWidget()) { + m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode(); + } } void deactivate() { BaseClass::deactivate(); m_modeConnections.clear(); } QWidget* createOptionWidget() { KisCanvas2* canvas = dynamic_cast(this->canvas()); Q_ASSERT(canvas); m_widgetHelper.createOptionWidget(canvas, this->toolId()); this->connect(this, SIGNAL(isActiveChanged(bool)), &m_widgetHelper, SLOT(slotToolActivatedChanged(bool))); this->connect(&m_widgetHelper, SIGNAL(selectionActionChanged(int)), this, SLOT(resetCursorStyle())); updateActionShortcutToolTips(); + if (isPixelOnly() && m_widgetHelper.optionWidget()) { + m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode(); + } return m_widgetHelper.optionWidget(); } SelectionMode selectionMode() const { return m_widgetHelper.selectionMode(); } SelectionAction selectionAction() const { if (alternateSelectionAction() == SELECTION_DEFAULT) { return m_widgetHelper.selectionAction(); } return alternateSelectionAction(); } bool antiAliasSelection() const { return m_widgetHelper.antiAliasSelection(); } SelectionAction alternateSelectionAction() const { return m_selectionActionAlternate; } KisSelectionOptions* selectionOptionWidget() { return m_widgetHelper.optionWidget(); } virtual void setAlternateSelectionAction(SelectionAction action) { m_selectionActionAlternate = action; dbgKrita << "Changing to selection action" << m_selectionActionAlternate; } void activateAlternateAction(KisTool::AlternateAction action) { Q_UNUSED(action); BaseClass::activatePrimaryAction(); } void deactivateAlternateAction(KisTool::AlternateAction action) { Q_UNUSED(action); BaseClass::deactivatePrimaryAction(); } void beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); beginPrimaryAction(event); } void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); continuePrimaryAction(event); } void endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); endPrimaryAction(event); } KisNodeSP locateSelectionMaskUnderCursor(const QPointF &pos, Qt::KeyboardModifiers modifiers) { if (modifiers != Qt::NoModifier) return 0; KisCanvas2* canvas = dynamic_cast(this->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas, 0); KisSelectionSP selection = canvas->viewManager()->selection(); if (selection && selection->outlineCacheValid()) { const qreal handleRadius = qreal(this->handleRadius()) / canvas->coordinatesConverter()->effectiveZoom(); QPainterPath samplePath; samplePath.addEllipse(pos, handleRadius, handleRadius); const QPainterPath selectionPath = selection->outlineCache(); if (selectionPath.intersects(samplePath) && !selectionPath.contains(samplePath)) { KisNodeSP parent = selection->parentNode(); if (parent && parent->isEditable()) { return parent; } } } return 0; } void keyPressEvent(QKeyEvent *event) { if (this->mode() != KisTool::PAINT_MODE) { setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); this->resetCursorStyle(); } BaseClass::keyPressEvent(event); } void keyReleaseEvent(QKeyEvent *event) { if (this->mode() != KisTool::PAINT_MODE) { setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); this->resetCursorStyle(); } BaseClass::keyPressEvent(event); } void mouseMoveEvent(KoPointerEvent *event) { if (!this->hasUserInteractionRunning() && (m_moveStrokeId || this->mode() != KisTool::PAINT_MODE)) { const QPointF pos = this->convertToPixelCoord(event->point); KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers()); if (selectionMask) { this->useCursor(KisCursor::moveSelectionCursor()); } else { setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); this->resetCursorStyle(); } } BaseClass::mouseMoveEvent(event); } virtual void beginPrimaryAction(KoPointerEvent *event) { if (!this->hasUserInteractionRunning()) { const QPointF pos = this->convertToPixelCoord(event->point); KisCanvas2* canvas = dynamic_cast(this->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers()); if (selectionMask) { KisStrokeStrategy *strategy = new MoveStrokeStrategy({selectionMask}, this->image().data(), this->image().data()); m_moveStrokeId = this->image()->startStroke(strategy); m_dragStartPos = pos; m_didMove = true; return; } } m_didMove = false; keysAtStart = event->modifiers(); setAlternateSelectionAction(KisSelectionModifierMapper::map(keysAtStart)); if (alternateSelectionAction() != SELECTION_DEFAULT) { BaseClass::listenToModifiers(false); } BaseClass::beginPrimaryAction(event); } virtual void continuePrimaryAction(KoPointerEvent *event) { if (m_moveStrokeId) { const QPointF pos = this->convertToPixelCoord(event->point); const QPoint offset((pos - m_dragStartPos).toPoint()); this->image()->addJob(m_moveStrokeId, new MoveStrokeStrategy::Data(offset)); return; } //If modifier keys have changed, tell the base tool it can start capturing modifiers if ((keysAtStart != event->modifiers()) && !BaseClass::listeningToModifiers()) { BaseClass::listenToModifiers(true); } //Always defer to the base class if it signals it is capturing modifier keys if (!BaseClass::listeningToModifiers()) { setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); } BaseClass::continuePrimaryAction(event); } void endPrimaryAction(KoPointerEvent *event) { if (m_moveStrokeId) { this->image()->endStroke(m_moveStrokeId); m_moveStrokeId.clear(); return; } keysAtStart = Qt::NoModifier; //reset this with each action BaseClass::endPrimaryAction(event); } bool selectionDragInProgress() const { return m_moveStrokeId; } bool selectionDidMove() const { return m_didMove; } QMenu* popupActionsMenu() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, 0); return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); } protected: using BaseClass::canvas; KisSelectionToolConfigWidgetHelper m_widgetHelper; SelectionAction m_selectionActionAlternate; + virtual bool isPixelOnly() const { + return false; + } + private: Qt::KeyboardModifiers keysAtStart; QPointF m_dragStartPos; KisStrokeId m_moveStrokeId; bool m_didMove = false; KisSignalAutoConnectionsStore m_modeConnections; }; struct FakeBaseTool : KisTool { FakeBaseTool(KoCanvasBase* canvas) : KisTool(canvas, QCursor()) { } FakeBaseTool(KoCanvasBase* canvas, const QString &toolName) : KisTool(canvas, QCursor()) { Q_UNUSED(toolName); } FakeBaseTool(KoCanvasBase* canvas, const QCursor &cursor) : KisTool(canvas, cursor) { } bool hasUserInteractionRunning() const { return false; } }; typedef KisToolSelectBase KisToolSelect; #endif // KISTOOLSELECTBASE_H diff --git a/libs/ui/widgets/KoFillConfigWidget.cpp b/libs/ui/widgets/KoFillConfigWidget.cpp index 75323e75db..11938edf3e 100644 --- a/libs/ui/widgets/KoFillConfigWidget.cpp +++ b/libs/ui/widgets/KoFillConfigWidget.cpp @@ -1,898 +1,898 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2012 Jean-Nicolas Artaud * * 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 "KoFillConfigWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceServerProvider.h" #include "KoResourceServerAdapter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoZoomHandler.h" #include "KoColorPopupButton.h" #include "ui_KoFillConfigWidget.h" #include #include #include #include #include "kis_canvas_resource_provider.h" #include #include #include #include "kis_global.h" #include "kis_debug.h" static const char* const buttonnone[]={ "16 16 3 1", "# c #000000", "e c #ff0000", "- c #ffffff", "################", "#--------------#", "#-e----------e-#", "#--e--------e--#", "#---e------e---#", "#----e----e----#", "#-----e--e-----#", "#------ee------#", "#------ee------#", "#-----e--e-----#", "#----e----e----#", "#---e------e---#", "#--e--------e--#", "#-e----------e-#", "#--------------#", "################"}; static const char* const buttonsolid[]={ "16 16 2 1", "# c #000000", ". c #969696", "################", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "################"}; // FIXME: Smoother gradient button. static const char* const buttongradient[]={ "16 16 15 1", "# c #000000", "n c #101010", "m c #202020", "l c #303030", "k c #404040", "j c #505050", "i c #606060", "h c #707070", "g c #808080", "f c #909090", "e c #a0a0a0", "d c #b0b0b0", "c c #c0c0c0", "b c #d0d0d0", "a c #e0e0e0", "################", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "################"}; static const char* const buttonpattern[]={ "16 16 4 1", ". c #0a0a0a", "# c #333333", "a c #a0a0a0", "b c #ffffffff", "################", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "################"}; class Q_DECL_HIDDEN KoFillConfigWidget::Private { public: Private(KoFlake::FillVariant _fillVariant) : canvas(0), colorChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), gradientChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), shapeChangedCompressor(200,KisSignalCompressor::FIRST_ACTIVE), fillVariant(_fillVariant), noSelectionTrackingMode(false) { } KoColorPopupAction *colorAction; KoResourcePopupAction *gradientAction; KoResourcePopupAction *patternAction; QButtonGroup *group; KoCanvasBase *canvas; KisSignalCompressor colorChangedCompressor; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; KoFillConfigWidget::StyleButton selectedFillIndex {KoFillConfigWidget::None}; QSharedPointer activeGradient; KisSignalCompressor gradientChangedCompressor; KisSignalCompressor shapeChangedCompressor; KoFlake::FillVariant fillVariant; QList previousShapeSelected;/// container to see if the selection has actually changed bool noSelectionTrackingMode; Ui_KoFillConfigWidget *ui; std::vector deactivationLocks; }; KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, bool trackShapeSelection, QWidget *parent) : QWidget(parent) , d(new Private(fillVariant)) { d->canvas = canvas; if (trackShapeSelection) { d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), &d->shapeChangedCompressor, SLOT(start())); d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), &d->shapeChangedCompressor, SLOT(start())); connect(&d->shapeChangedCompressor, SIGNAL(timeout()), this, SLOT(shapeChanged())); } d->resourceManagerAcyclicConnector.connectBackwardResourcePair( d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); d->resourceManagerAcyclicConnector.connectForwardVoid( this, SIGNAL(sigInternalRequestColorToResourceManager()), this, SLOT(slotProposeCurrentColorToResourceManager())); // configure GUI d->ui = new Ui_KoFillConfigWidget(); d->ui->setupUi(this); d->group = new QButtonGroup(this); d->group->setExclusive(true); d->ui->btnNoFill->setIcon(QPixmap((const char **) buttonnone)); d->group->addButton(d->ui->btnNoFill, None); d->ui->btnSolidFill->setIcon(QPixmap((const char **) buttonsolid)); d->group->addButton(d->ui->btnSolidFill, Solid); d->ui->btnGradientFill->setIcon(QPixmap((const char **) buttongradient)); d->group->addButton(d->ui->btnGradientFill, Gradient); d->ui->btnPatternFill->setIcon(QPixmap((const char **) buttonpattern)); d->group->addButton(d->ui->btnPatternFill, Pattern); d->ui->btnPatternFill->setVisible(false); d->colorAction = new KoColorPopupAction(d->ui->btnChooseSolidColor); d->colorAction->setToolTip(i18n("Change the filling color")); d->colorAction->setCurrentColor(Qt::white); d->ui->btnChooseSolidColor->setDefaultAction(d->colorAction); d->ui->btnChooseSolidColor->setPopupMode(QToolButton::InstantPopup); d->ui->btnSolidColorPick->setIcon(KisIconUtils::loadIcon("krita_tool_color_picker")); // TODO: for now the color picking button is disabled! d->ui->btnSolidColorPick->setEnabled(false); d->ui->btnSolidColorPick->setVisible(false); connect(d->colorAction, SIGNAL(colorChanged(KoColor)), &d->colorChangedCompressor, SLOT(start())); connect(&d->colorChangedCompressor, SIGNAL(timeout()), SLOT(colorChanged())); connect(d->ui->btnChooseSolidColor, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon())); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(styleButtonPressed(int))); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(slotUpdateFillTitle())); slotUpdateFillTitle(); styleButtonPressed(d->group->checkedId()); // Gradient selector d->ui->wdgGradientEditor->setCompactMode(true); connect(d->ui->wdgGradientEditor, SIGNAL(sigGradientChanged()), &d->gradientChangedCompressor, SLOT(start())); connect(&d->gradientChangedCompressor, SIGNAL(timeout()), SLOT(activeGradientChanged())); KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); QSharedPointer gradientResourceAdapter( new KoResourceServerAdapter(serverProvider->gradientServer())); d->gradientAction = new KoResourcePopupAction(gradientResourceAdapter, d->ui->btnChoosePredefinedGradient); d->gradientAction->setToolTip(i18n("Change filling gradient")); d->ui->btnChoosePredefinedGradient->setDefaultAction(d->gradientAction); d->ui->btnChoosePredefinedGradient->setPopupMode(QToolButton::InstantPopup); connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer)), SLOT(gradientResourceChanged())); connect(d->ui->btnChoosePredefinedGradient, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon())); d->ui->btnSaveGradient->setIcon(KisIconUtils::loadIcon("document-save")); connect(d->ui->btnSaveGradient, SIGNAL(clicked()), SLOT(slotSavePredefinedGradientClicked())); connect(d->ui->cmbGradientRepeat, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientRepeatChanged())); connect(d->ui->cmbGradientType, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientTypeChanged())); deactivate(); #if 0 // Pattern selector QSharedPointerpatternResourceAdapter(new KoResourceServerAdapter(serverProvider->patternServer())); d->patternAction = new KoResourcePopupAction(patternResourceAdapter, d->colorButton); d->patternAction->setToolTip(i18n("Change the filling pattern")); connect(d->patternAction, SIGNAL(resourceSelected(QSharedPointer)), this, SLOT(patternChanged(QSharedPointer))); connect(d->colorButton, SIGNAL(iconSizeChanged()), d->patternAction, SLOT(updateIcon())); #endif } KoFillConfigWidget::~KoFillConfigWidget() { delete d; } void KoFillConfigWidget::activate() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); if (!d->noSelectionTrackingMode) { d->shapeChangedCompressor.start(); } else { loadCurrentFillFromResourceServer(); } updateWidgetComponentVisbility(); } void KoFillConfigWidget::deactivate() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); } void KoFillConfigWidget::forceUpdateOnSelectionChanged() { d->shapeChangedCompressor.start(); } void KoFillConfigWidget::setNoSelectionTrackingMode(bool value) { d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { d->shapeChangedCompressor.start(); } } void KoFillConfigWidget::slotUpdateFillTitle() { QString text = d->group->checkedButton() ? d->group->checkedButton()->text() : QString(); text.replace('&', QString()); d->ui->lblFillTitle->setText(text); } void KoFillConfigWidget::slotCanvasResourceChanged(int key, const QVariant &value) { if ((key == KoCanvasResourceProvider::ForegroundColor && d->fillVariant == KoFlake::Fill) || (key == KoCanvasResourceProvider::BackgroundColor && d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) || (key == KoCanvasResourceProvider::ForegroundColor && d->noSelectionTrackingMode)) { KoColor color = value.value(); const int checkedId = d->group->checkedId(); if ((checkedId < 0 || checkedId == None || checkedId == Solid) && !(checkedId == Solid && d->colorAction->currentKoColor() == color)) { d->group->button(Solid)->setChecked(true); d->selectedFillIndex = Solid; d->colorAction->setCurrentColor(color); d->colorChangedCompressor.start(); } else if (checkedId == Gradient && key == KoCanvasResourceProvider::ForegroundColor) { d->ui->wdgGradientEditor->notifyGlobalColorChanged(color); } } else if (key == KisCanvasResourceProvider::CurrentGradient) { KoResource *gradient = value.value(); const int checkedId = d->group->checkedId(); if (gradient && (checkedId < 0 || checkedId == None || checkedId == Gradient)) { d->group->button(Gradient)->setChecked(true); d->gradientAction->setCurrentResource(gradient); } } } QList KoFillConfigWidget::currentShapes() { return d->canvas->selectedShapesProxy()->selection()->selectedEditableShapes(); } int KoFillConfigWidget::selectedFillIndex() { return d->selectedFillIndex; } void KoFillConfigWidget::styleButtonPressed(int buttonId) { QList shapes = currentShapes(); switch (buttonId) { case KoFillConfigWidget::None: noColorSelected(); break; case KoFillConfigWidget::Solid: colorChanged(); break; case KoFillConfigWidget::Gradient: if (d->activeGradient) { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } else { gradientResourceChanged(); } break; case KoFillConfigWidget::Pattern: // Only select mode in the widget, don't set actual pattern :/ //d->colorButton->setDefaultAction(d->patternAction); //patternChanged(d->patternAction->currentBackground()); break; } // update tool option fields with first selected object if (shapes.isEmpty() == false) { KoShape *firstShape = shapes.first(); updateFillIndexFromShape(firstShape); updateFillColorFromShape(firstShape); } updateWidgetComponentVisbility(); } KoShapeStrokeSP KoFillConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(new KoShapeStroke()); KIS_ASSERT_RECOVER_RETURN_VALUE(d->fillVariant == KoFlake::StrokeFill, stroke); switch (d->group->checkedId()) { case KoFillConfigWidget::None: stroke->setColor(Qt::transparent); break; case KoFillConfigWidget::Solid: stroke->setColor(d->colorAction->currentColor()); break; case KoFillConfigWidget::Gradient: { QScopedPointer g(d->activeGradient->toQGradient()); QBrush newBrush = *g; stroke->setLineBrush(newBrush); stroke->setColor(Qt::transparent); break; } case KoFillConfigWidget::Pattern: break; } return stroke; } void KoFillConfigWidget::noColorSelected() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(QColor()); if (command) { d->canvas->addCommand(command); } if (d->fillVariant == KoFlake::StrokeFill) { KUndo2Command *lineCommand = wrapper.setLineWidth(0.0); if (lineCommand) { d->canvas->addCommand(lineCommand); } } emit sigFillChanged(); } void KoFillConfigWidget::colorChanged() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigInternalRequestColorToResourceManager(); emit sigFillChanged(); return; } KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor()); if (command) { d->canvas->addCommand(command); } // only returns true if it is a stroke object that has a 0 for line width if (wrapper.hasZeroLineWidth() ) { KUndo2Command *lineCommand = wrapper.setLineWidth(1.0); if (lineCommand) { d->canvas->addCommand(lineCommand); } // * line to test out QColor solidColor = d->colorAction->currentColor(); solidColor.setAlpha(255); command = wrapper.setColor(solidColor); if (command) { d->canvas->addCommand(command); } } d->colorAction->setCurrentColor(wrapper.color()); emit sigFillChanged(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::slotProposeCurrentColorToResourceManager() { const int checkedId = d->group->checkedId(); bool hasColor = false; KoColor color; KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor; if (checkedId == Solid) { if (d->fillVariant == KoFlake::StrokeFill) { colorSlot = KoCanvasResourceProvider::BackgroundColor; } color = d->colorAction->currentKoColor(); hasColor = true; } else if (checkedId == Gradient) { if (boost::optional gradientColor = d->ui->wdgGradientEditor->currentActiveStopColor()) { color = *gradientColor; hasColor = true; } } if (hasColor) { /** * Don't let opacity leak to our resource manager system * * NOTE: theoretically, we could guarantee it on a level of the * resource manager itself, */ color.setOpacity(OPACITY_OPAQUE_U8); d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color)); } } template QString findFirstAvailableResourceName(const QString &baseName, ResourceServer *server) { if (!server->resourceByName(baseName)) return baseName; int counter = 1; QString result; while ((result = QString("%1%2").arg(baseName).arg(counter)), server->resourceByName(result)) { counter++; } return result; } void KoFillConfigWidget::slotSavePredefinedGradientClicked() { KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); auto server = serverProvider->gradientServer(); const QString defaultGradientNamePrefix = i18nc("default prefix for the saved gradient", "gradient"); QString name = d->activeGradient->name().isEmpty() ? defaultGradientNamePrefix : d->activeGradient->name(); name = findFirstAvailableResourceName(name, server); name = QInputDialog::getText(this, i18nc("@title:window", "Save Gradient"), i18n("Enter gradient name:"), QLineEdit::Normal, name); // TODO: currently we do not allow the user to // create two resources with the same name! // Please add some feedback for it! name = findFirstAvailableResourceName(name, server); d->activeGradient->setName(name); const QString saveLocation = server->saveLocation(); d->activeGradient->setFilename(saveLocation + d->activeGradient->name() + d->activeGradient->defaultFileExtension()); KoAbstractGradient *newGradient = d->activeGradient->clone(); server->addResource(newGradient); d->gradientAction->setCurrentResource(newGradient); } void KoFillConfigWidget::activeGradientChanged() { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::gradientResourceChanged() { QSharedPointer bg = qSharedPointerDynamicCast( d->gradientAction->currentBackground()); uploadNewGradientBackground(bg->gradient()); setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } void KoFillConfigWidget::slotGradientTypeChanged() { QGradient::Type type = d->ui->cmbGradientType->currentIndex() == 0 ? QGradient::LinearGradient : QGradient::RadialGradient; d->activeGradient->setType(type); activeGradientChanged(); } void KoFillConfigWidget::slotGradientRepeatChanged() { QGradient::Spread spread = QGradient::Spread(d->ui->cmbGradientRepeat->currentIndex()); d->activeGradient->setSpread(spread); activeGradientChanged(); } void KoFillConfigWidget::uploadNewGradientBackground(const QGradient *gradient) { KisSignalsBlocker b1(d->ui->wdgGradientEditor, d->ui->cmbGradientType, d->ui->cmbGradientRepeat); d->ui->wdgGradientEditor->setGradient(0); d->activeGradient.reset(KoStopGradient::fromQGradient(gradient)); d->ui->wdgGradientEditor->setGradient(d->activeGradient.data()); d->ui->cmbGradientType->setCurrentIndex(d->activeGradient->type() != QGradient::LinearGradient); d->ui->cmbGradientRepeat->setCurrentIndex(int(d->activeGradient->spread())); } void KoFillConfigWidget::setNewGradientBackgroundToShape() { QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); QScopedPointer srcQGradient(d->activeGradient->toQGradient()); KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data()); if (command) { d->canvas->addCommand(command); } emit sigFillChanged(); } void KoFillConfigWidget::updateGradientSaveButtonAvailability() { bool savingEnabled = false; QScopedPointer currentGradient(d->activeGradient->toQGradient()); QSharedPointer bg = d->gradientAction->currentBackground(); if (bg) { QSharedPointer resourceBackground = qSharedPointerDynamicCast(bg); savingEnabled = resourceBackground->gradient()->stops() != currentGradient->stops(); savingEnabled |= resourceBackground->gradient()->type() != currentGradient->type(); savingEnabled |= resourceBackground->gradient()->spread() != currentGradient->spread(); } d->ui->btnSaveGradient->setEnabled(savingEnabled); } void KoFillConfigWidget::patternChanged(QSharedPointer background) { Q_UNUSED(background); #if 0 QSharedPointer patternBackground = qSharedPointerDynamicCast(background); if (! patternBackground) { return; } QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { return; } KoImageCollection *imageCollection = d->canvas->shapeController()->resourceManager()->imageCollection(); if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); fill->setPattern(patternBackground->pattern()); d->canvas->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill)); } #endif } void KoFillConfigWidget::loadCurrentFillFromResourceServer() { { KoColor color = d->canvas->resourceManager()->backgroundColor(); slotCanvasResourceChanged(KoCanvasResourceProvider::BackgroundColor, QVariant::fromValue(color)); } { KoColor color = d->canvas->resourceManager()->foregroundColor(); slotCanvasResourceChanged(KoCanvasResourceProvider::ForegroundColor, QVariant::fromValue(color)); } Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } emit sigFillChanged(); } void KoFillConfigWidget::shapeChanged() { if (d->noSelectionTrackingMode) return; QList shapes = currentShapes(); // check to see if the shape actually changed...or is still the same shape if (d->previousShapeSelected == shapes) { return; } else { d->previousShapeSelected = shapes; } if (shapes.isEmpty() || (shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) { Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(!shapes.isEmpty()); } } else { // only one vector object selected Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } // update active index of shape KoShape *shape = shapes.first(); updateFillIndexFromShape(shape); updateFillColorFromShape(shape); // updates tool options fields } // updates the UI d->group->button(d->selectedFillIndex)->setChecked(true); updateWidgetComponentVisbility(); slotUpdateFillTitle(); } void KoFillConfigWidget::updateFillIndexFromShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: d->selectedFillIndex = KoFillConfigWidget::None; break; case KoFlake::Solid: d->selectedFillIndex = KoFillConfigWidget::Solid; break; case KoFlake::Gradient: d->selectedFillIndex = KoFillConfigWidget::Gradient; break; case KoFlake::Pattern: d->selectedFillIndex = KoFillConfigWidget::Pattern; break; } } void KoFillConfigWidget::updateFillColorFromShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: break; case KoFlake::Solid: { QColor color = wrapper.color(); if (color.alpha() > 0) { d->colorAction->setCurrentColor(wrapper.color()); } break; } case KoFlake::Gradient: uploadNewGradientBackground(wrapper.gradient()); updateGradientSaveButtonAvailability(); break; case KoFlake::Pattern: break; } } void KoFillConfigWidget::updateWidgetComponentVisbility() { // The UI is showing/hiding things like this because the 'stacked widget' isn't very flexible // and makes it difficult to put anything underneath it without a lot empty space // hide everything first d->ui->wdgGradientEditor->setVisible(false); d->ui->btnChoosePredefinedGradient->setVisible(false); d->ui->btnChooseSolidColor->setVisible(false); d->ui->typeLabel->setVisible(false); d->ui->repeatLabel->setVisible(false); d->ui->cmbGradientRepeat->setVisible(false); d->ui->cmbGradientType->setVisible(false); d->ui->btnSolidColorPick->setVisible(false); d->ui->btnSaveGradient->setVisible(false); d->ui->gradientTypeLine->setVisible(false); d->ui->soldStrokeColorLabel->setVisible(false); d->ui->presetLabel->setVisible(false); // keep options hidden if no vector shapes are selected if(currentShapes().isEmpty()) { return; } switch (d->selectedFillIndex) { case KoFillConfigWidget::None: break; case KoFillConfigWidget::Solid: d->ui->btnChooseSolidColor->setVisible(true); - d->ui->btnSolidColorPick->setVisible(true); + d->ui->btnSolidColorPick->setVisible(false); d->ui->soldStrokeColorLabel->setVisible(true); break; case KoFillConfigWidget::Gradient: d->ui->wdgGradientEditor->setVisible(true); d->ui->btnChoosePredefinedGradient->setVisible(true); d->ui->typeLabel->setVisible(true); d->ui->repeatLabel->setVisible(true); d->ui->cmbGradientRepeat->setVisible(true); d->ui->cmbGradientType->setVisible(true); d->ui->btnSaveGradient->setVisible(true); d->ui->gradientTypeLine->setVisible(true); d->ui->presetLabel->setVisible(true); break; case KoFillConfigWidget::Pattern: break; } } diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc index 3e42dc1ba8..1dbffa4274 100644 --- a/libs/ui/widgets/kis_selection_options.cc +++ b/libs/ui/widgets/kis_selection_options.cc @@ -1,187 +1,193 @@ /* * Copyright (c) 2005 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 "kis_selection_options.h" #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_device.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include #include KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/) { m_page = new WdgSelectionOptions(this); Q_CHECK_PTR(m_page); QVBoxLayout * l = new QVBoxLayout(this); l->addWidget(m_page); l->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Preferred, QSizePolicy::Expanding)); l->setContentsMargins(0,0,0,0); m_mode = new QButtonGroup(this); m_mode->addButton(m_page->pixel, PIXEL_SELECTION); m_mode->addButton(m_page->shape, SHAPE_PROTECTION); m_action = new QButtonGroup(this); m_action->addButton(m_page->add, SELECTION_ADD); m_action->addButton(m_page->subtract, SELECTION_SUBTRACT); m_action->addButton(m_page->replace, SELECTION_REPLACE); m_action->addButton(m_page->intersect, SELECTION_INTERSECT); m_action->addButton(m_page->symmetricdifference, SELECTION_SYMMETRICDIFFERENCE); m_page->pixel->setGroupPosition(KoGroupButton::GroupLeft); m_page->shape->setGroupPosition(KoGroupButton::GroupRight); m_page->pixel->setIcon(KisIconUtils::loadIcon("select_pixel")); m_page->shape->setIcon(KisIconUtils::loadIcon("select_shape")); m_page->add->setGroupPosition(KoGroupButton::GroupCenter); m_page->subtract->setGroupPosition(KoGroupButton::GroupCenter); m_page->replace->setGroupPosition(KoGroupButton::GroupLeft); m_page->intersect->setGroupPosition(KoGroupButton::GroupCenter); m_page->symmetricdifference->setGroupPosition(KoGroupButton::GroupRight); m_page->add->setIcon(KisIconUtils::loadIcon("selection_add")); m_page->subtract->setIcon(KisIconUtils::loadIcon("selection_subtract")); m_page->replace->setIcon(KisIconUtils::loadIcon("selection_replace")); m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect")); m_page->symmetricdifference->setIcon(KisIconUtils::loadIcon("selection_symmetric_difference")); connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int))); connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int))); connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int))); connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool))); KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); m_page->chkAntiAliasing->setChecked(cfg.readEntry("antiAliasSelection", true)); } KisSelectionOptions::~KisSelectionOptions() { } int KisSelectionOptions::action() { return m_action->checkedId(); } void KisSelectionOptions::setAction(int action) { QAbstractButton* button = m_action->button(action); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } void KisSelectionOptions::setMode(int mode) { QAbstractButton* button = m_mode->button(mode); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); hideActionsForSelectionMode(mode); } void KisSelectionOptions::setAntiAliasSelection(bool value) { m_page->chkAntiAliasing->setChecked(value); } +void KisSelectionOptions::enablePixelOnlySelectionMode() +{ + setMode(PIXEL_SELECTION); + disableSelectionModeOption(); +} + void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut) { const QString shortcutString = shortcut.toString(QKeySequence::NativeText); QString toolTipText; switch ((SelectionAction)action) { case SELECTION_DEFAULT: case SELECTION_REPLACE: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Replace") : i18nc("@info:tooltip", "Replace (%1)", shortcutString); m_action->button(SELECTION_REPLACE)->setToolTip(toolTipText); break; case SELECTION_ADD: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Add") : i18nc("@info:tooltip", "Add (%1)", shortcutString); m_action->button(SELECTION_ADD)->setToolTip(toolTipText); break; case SELECTION_SUBTRACT: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Subtract") : i18nc("@info:tooltip", "Subtract (%1)", shortcutString); m_action->button(SELECTION_SUBTRACT)->setToolTip(toolTipText); break; case SELECTION_INTERSECT: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Intersect") : i18nc("@info:tooltip", "Intersect (%1)", shortcutString); m_action->button(SELECTION_INTERSECT)->setToolTip(toolTipText); break; case SELECTION_SYMMETRICDIFFERENCE: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Symmetric Difference") : i18nc("@info:tooltip", "Symmetric Difference (%1)", shortcutString); m_action->button(SELECTION_SYMMETRICDIFFERENCE)->setToolTip(toolTipText); break; } } //hide action buttons and antialiasing, if shape selection is active (actions currently don't work on shape selection) void KisSelectionOptions::hideActionsForSelectionMode(int mode) { const bool isPixelSelection = (mode == (int)PIXEL_SELECTION); m_page->chkAntiAliasing->setVisible(isPixelSelection); } bool KisSelectionOptions::antiAliasSelection() { return m_page->chkAntiAliasing->isChecked(); } void KisSelectionOptions::disableAntiAliasSelectionOption() { m_page->chkAntiAliasing->hide(); disconnect(m_page->pixel, SIGNAL(clicked()), m_page->chkAntiAliasing, SLOT(show())); } void KisSelectionOptions::disableSelectionModeOption() { m_page->lblMode->hide(); m_page->pixel->hide(); m_page->shape->hide(); } diff --git a/libs/ui/widgets/kis_selection_options.h b/libs/ui/widgets/kis_selection_options.h index 32d20c2889..9a8065d60b 100644 --- a/libs/ui/widgets/kis_selection_options.h +++ b/libs/ui/widgets/kis_selection_options.h @@ -1,79 +1,80 @@ /* * Copyright (c) 2005 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 __KIS_SELECTION_OPTIONS_H__ #define __KIS_SELECTION_OPTIONS_H__ #include #include "kritaui_export.h" #include "ui_wdgselectionoptions.h" class KisCanvas2; class QButtonGroup; class QKeySequence; class WdgSelectionOptions : public QWidget, public Ui::WdgSelectionOptions { Q_OBJECT public: WdgSelectionOptions(QWidget *parent) : QWidget(parent) { setupUi(this); } }; /** */ class KRITAUI_EXPORT KisSelectionOptions : public QWidget { Q_OBJECT public: KisSelectionOptions(KisCanvas2 * subject); ~KisSelectionOptions() override; int action(); bool antiAliasSelection(); void disableAntiAliasSelectionOption(); void disableSelectionModeOption(); void setAction(int); void setMode(int); void setAntiAliasSelection(bool value); + void enablePixelOnlySelectionMode(); void updateActionButtonToolTip(int action, const QKeySequence &shortcut); Q_SIGNALS: void actionChanged(int); void modeChanged(int); void antiAliasSelectionChanged(bool); private Q_SLOTS: void hideActionsForSelectionMode(int mode); private: WdgSelectionOptions * m_page; QButtonGroup* m_mode; QButtonGroup* m_action; }; #endif diff --git a/plugins/assistants/Assistants/kis_assistant_tool.cc b/plugins/assistants/Assistants/kis_assistant_tool.cc index 204e1d5144..7cd15bec72 100644 --- a/plugins/assistants/Assistants/kis_assistant_tool.cc +++ b/plugins/assistants/Assistants/kis_assistant_tool.cc @@ -1,1073 +1,1061 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2017 Scott Petrovic * * 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; version 2.1 of the License. * * 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 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 "kis_dom_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "VanishingPointAssistant.h" #include "EditAssistantsCommand.h" #include #include KisAssistantTool::KisAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0) { Q_ASSERT(m_canvas); setObjectName("tool_assistanttool"); } KisAssistantTool::~KisAssistantTool() { } void KisAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; m_handleSize = 17; m_canvas->paintingAssistantsDecoration()->setHandleSize(m_handleSize); if (m_optionsWidget) { m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } m_canvas->updateCanvas(); } void KisAssistantTool::deactivate() { m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); - m_origAssistantList = cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()); + m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()); bool newAssistantAllowed = true; KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); if (m_newAssistant) { m_internalMode = MODE_CREATION; *m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false); if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); // syncs the assistant handles to the handles reference we store in this tool // they can get out of sync with the way the actions and paintevents occur // we probably need to stop storing a reference in m_handles and call the assistants directly m_handles = m_canvas->paintingAssistantsDecoration()->handles(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // find out which handle on all assistants is closest to the mouse position // vanishing points have "side handles", so make sure to include that { QList allAssistantHandles; allAssistantHandles.append(assistant->handles()); allAssistantHandles.append(assistant->sideHandles()); Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; assistantSelected(assistant); // whatever handle is the closest contains the selected assistant } } } if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed if (m_handleDrag == assistant->topLeft()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL ); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || assistant->id() == "concentric ellipse" || assistant->id() == "fisheye-point")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->buttonPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->buttonPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // This code contains the click event behavior. QTransform initialTransform = m_canvas->coordinatesConverter()->documentToWidgetTransform(); QPointF actionsPosition = initialTransform.map(assistant->buttonPosition()); // for UI editor widget controls with move, show, and delete -- disregard document transforms like rotating and mirroring. // otherwise the UI controls get awkward to use when they are at 45 degree angles or the order of controls gets flipped backwards QPointF uiMousePosition = initialTransform.map( canvasDecoration->snapToGuide(event, QPointF(), false)); AssistantEditorData editorShared; // shared position data between assistant tool and decoration QPointF iconMovePosition(actionsPosition + editorShared.moveIconPosition); QPointF iconSnapPosition(actionsPosition + editorShared.snapIconPosition); QPointF iconDeletePosition(actionsPosition + editorShared.deleteIconPosition); QRectF deleteRect(iconDeletePosition, QSizeF(editorShared.deleteIconSize, editorShared.deleteIconSize)); QRectF visibleRect(iconSnapPosition, QSizeF(editorShared.snapIconSize, editorShared.snapIconSize)); QRectF moveRect(iconMovePosition, QSizeF(editorShared.moveIconSize, editorShared.moveIconSize)); if (moveRect.contains(uiMousePosition)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; assistantSelected(assistant); // whatever handle is the closest contains the selected assistant return; } if (deleteRect.contains(uiMousePosition)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(uiMousePosition)) { newAssistantAllowed = false; assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle assistant->uncache();//this updates the chache of the assistant, very important. assistantSelected(assistant); // whatever handle is the closest contains the selected assistant } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visibility// QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); if (m_newAssistant->numHandles() <= 1) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } } if (m_newAssistant) { m_newAssistant->setAssistantGlobalColorCache(m_canvas->paintingAssistantsDecoration()->globalAssistantsColor()); } m_canvas->updateCanvas(); } void KisAssistantTool::continuePrimaryAction(KoPointerEvent *event) { KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { *m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { if (handle == m_handleDrag) continue; double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } if (m_assistantDrag->id()== "vanishing point"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { if ((m_higlightedNode = assistant->closestCornerHandleFromPoint(mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. if (m_handleDrag == assistant->sideHandles()[0]) { QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); if (length < 2.0){ length = 2.0; } length += perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[1] = perspectiveline.p2(); } else if (m_handleDrag == assistant->sideHandles()[2]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){ length=2.0; } length += perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[3] = perspectiveline.p2(); } // for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ QPointF vanishingpoint(0,0); QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ *assistant->handles()[0] = vanishingpoint; } }// and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length < 2.0) { length = 2.0; } if (length2 < 2.0) { length2=2.0; } length += perspectiveline.length(); length2 += perspectiveline2.length(); perspectiveline.setLength(length); perspectiveline2.setLength(length2); *assistant->sideHandles()[1] = perspectiveline.p2(); *assistant->sideHandles()[3] = perspectiveline2.p2(); } } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } void KisAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag || m_assistantDrag) { if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; } else { m_assistantDrag.clear(); } dbgUI << "creating undo command..."; - KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants())); + KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants())); m_canvas->viewManager()->undoAdapter()->addCommand(command); dbgUI << "done"; } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; } else { event->ignore(); } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } -QList KisAssistantTool::cloneAssistantList(const QList &list) const -{ - QMap handleMap; - QList clonedList; - dbgUI << "cloning assistants..."; - for (auto i = list.begin(); i != list.end(); ++i) { - clonedList << (*i)->clone(handleMap); - } - dbgUI << "done"; - return clonedList; -} - void KisAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid); } QList assistants = m_canvas->paintingAssistantsDecoration()->assistants(); - KUndo2Command *addAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, cloneAssistantList(assistants), EditAssistantsCommand::ADD, assistants.indexOf(m_newAssistant)); + KUndo2Command *addAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(assistants), EditAssistantsCommand::ADD, assistants.indexOf(m_newAssistant)); m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant); updateToolOptionsUI(); // vanishing point assistant will get an extra option m_newAssistant.clear(); } void KisAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { QList assistants = m_canvas->paintingAssistantsDecoration()->assistants(); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->canvasResourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); - KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()), EditAssistantsCommand::REMOVE, assistants.indexOf(assistant)); + KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()), EditAssistantsCommand::REMOVE, assistants.indexOf(assistant)); m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } void KisAssistantTool::assistantSelected(KisPaintingAssistantSP assistant) { m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(assistant); updateToolOptionsUI(); } void KisAssistantTool::updateToolOptionsUI() { KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); bool hasActiveAssistant = m_selectedAssistant ? true : false; if (m_selectedAssistant) { bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point"; m_options.vanishingPointAngleSpinbox->setVisible(isVanishingPointAssistant); if (isVanishingPointAssistant) { QSharedPointer assis = qSharedPointerCast(m_selectedAssistant); m_options.vanishingPointAngleSpinbox->setValue(assis->referenceLineDensity()); } // load custom color settings from assistant (this happens when changing assistant m_options.useCustomAssistantColor->setChecked(m_selectedAssistant->useCustomColor()); m_options.customAssistantColorButton->setColor(m_selectedAssistant->assistantCustomColor()); float opacity = (float)m_selectedAssistant->assistantCustomColor().alpha()/255.0 * 100.0 ; m_options.customColorOpacitySlider->setValue(opacity); } else { m_options.vanishingPointAngleSpinbox->setVisible(false); // } // show/hide elements if an assistant is selected or not m_options.assistantsGlobalOpacitySlider->setVisible(hasActiveAssistant); m_options.assistantsColor->setVisible(hasActiveAssistant); m_options.globalColorLabel->setVisible(hasActiveAssistant); m_options.useCustomAssistantColor->setVisible(hasActiveAssistant); // hide custom color options if use custom color is not selected bool showCustomColorSettings = m_options.useCustomAssistantColor->isChecked() && hasActiveAssistant; m_options.customColorOpacitySlider->setVisible(showCustomColorSettings); m_options.customAssistantColorButton->setVisible(showCustomColorSettings); // disable global color settings if we are using the custom color m_options.assistantsGlobalOpacitySlider->setEnabled(!showCustomColorSettings); m_options.assistantsColor->setEnabled(!showCustomColorSettings); m_options.globalColorLabel->setEnabled(!showCustomColorSettings); } void KisAssistantTool::slotChangeVanishingPointAngle(double value) { if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) { return; } // get the selected assistant and change the angle value KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point"; if (isVanishingPointAssistant) { QSharedPointer assis = qSharedPointerCast(m_selectedAssistant); assis->setReferenceLineDensity((float)value); } } m_canvas->canvasWidget()->update(); } void KisAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { QPointF translate = event->point - m_dragEnd; m_dragEnd = event->point; m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); } m_canvas->updateCanvas(); } void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())); // show special display while a new assistant is in the process of being created if (m_newAssistant) { QColor assistantColor = m_newAssistant->effectiveAssistantColor(); assistantColor.setAlpha(80); m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false, m_canvas, true, false); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize))); _gc.save(); _gc.setPen(Qt::NoPen); _gc.setBrush(assistantColor); _gc.drawPath(path); _gc.restore(); } } Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { QColor assistantColor = assistant->effectiveAssistantColor(); assistantColor.setAlpha(80); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { QRectF ellipse(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); // render handles differently if it is the one being dragged. if (handle == m_handleDrag || handle == m_handleCombine) { QPen stroke(assistantColor, 4); _gc.save(); _gc.setPen(stroke); _gc.setBrush(Qt::NoBrush); _gc.drawEllipse(ellipse); _gc.restore(); } } } } void KisAssistantTool::removeAllAssistants() { m_canvas->viewManager()->canvasResourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } void KisAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle, HandleType::NORMAL); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } // load custom shared assistant properties if ( xml.attributes().hasAttribute("useCustomColor")) { QStringRef useCustomColor = xml.attributes().value("useCustomColor"); bool usingColor = false; if (useCustomColor.toString() == "1") { usingColor = true; } assistant->setUseCustomColor(usingColor); } if ( xml.attributes().hasAttribute("useCustomColor")) { QStringRef customColor = xml.attributes().value("customColor"); assistant->setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) ); } } if (assistant) { assistant->loadCustomXml(&xml); } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point"){ //ideally we'd save and load side-handles as well, but this is all I've got// QPointF pos = *assistant->handles()[0]; assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeAttribute("color", KisDomUtils::qColorToQString( m_canvas->paintingAssistantsDecoration()->globalAssistantsColor())); // global color if no custom color used xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); xml.writeAttribute("useCustomColor", QString::number(assistant->useCustomColor())); xml.writeAttribute("customColor", KisDomUtils::qColorToQString(assistant->assistantCustomColor())); // custom assistant properties like angle density on vanishing point assistant->saveCustomXml(&xml); // handle information xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } QWidget *KisAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("document-open")); m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save")); m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { m_options.availableAssistantsComboBox->addItem(id.name(), id.id()); } connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants())); connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants())); connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); connect(m_options.assistantsColor, SIGNAL(changed(QColor)), SLOT(slotGlobalAssistantsColorChanged(QColor))); connect(m_options.assistantsGlobalOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotGlobalAssistantOpacityChanged())); connect(m_options.vanishingPointAngleSpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeVanishingPointAngle(double))); //ENTER_FUNCTION() << ppVar(m_canvas) << ppVar(m_canvas && m_canvas->paintingAssistantsDecoration()); // initialize UI elements with existing data if possible if (m_canvas && m_canvas->paintingAssistantsDecoration()) { const QColor color = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor(); QColor opaqueColor = color; opaqueColor.setAlpha(255); //ENTER_FUNCTION() << ppVar(opaqueColor); m_options.assistantsColor->setColor(opaqueColor); m_options.customAssistantColorButton->setColor(opaqueColor); m_options.assistantsGlobalOpacitySlider->setValue(color.alphaF() * 100.0); } else { m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants m_options.assistantsGlobalOpacitySlider->setValue(100); // 100% } m_options.assistantsGlobalOpacitySlider->setPrefix(i18n("Opacity: ")); m_options.assistantsGlobalOpacitySlider->setSuffix(" %"); // custom color of selected assistant m_options.customColorOpacitySlider->setValue(100); // 100% m_options.customColorOpacitySlider->setPrefix(i18n("Opacity: ")); m_options.customColorOpacitySlider->setSuffix(" %"); connect(m_options.useCustomAssistantColor, SIGNAL(clicked(bool)), this, SLOT(slotUpdateCustomColor())); connect(m_options.customAssistantColorButton, SIGNAL(changed(QColor)), this, SLOT(slotUpdateCustomColor())); connect(m_options.customColorOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotCustomOpacityChanged())); m_options.vanishingPointAngleSpinbox->setPrefix(i18n("Density: ")); m_options.vanishingPointAngleSpinbox->setSuffix(QChar(Qt::Key_degree)); m_options.vanishingPointAngleSpinbox->setRange(1.0, 180.0); m_options.vanishingPointAngleSpinbox->setSingleStep(0.5); m_options.vanishingPointAngleSpinbox->setVisible(false); } updateToolOptionsUI(); return m_optionsWidget; } void KisAssistantTool::slotGlobalAssistantsColorChanged(const QColor& setColor) { // color and alpha are stored separately, so we need to merge the values before sending it on int oldAlpha = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor().alpha(); QColor newColor = setColor; newColor.setAlpha(oldAlpha); m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor); m_canvas->paintingAssistantsDecoration()->uncache(); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotGlobalAssistantOpacityChanged() { QColor newColor = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor(); qreal newOpacity = m_options.assistantsGlobalOpacitySlider->value() * 0.01 * 255.0; newColor.setAlpha(int(newOpacity)); m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor); m_canvas->paintingAssistantsDecoration()->uncache(); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotUpdateCustomColor() { // get the selected assistant and change the angle value KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { m_selectedAssistant->setUseCustomColor(m_options.useCustomAssistantColor->isChecked()); // changing color doesn't keep alpha, so update that before we send it on QColor newColor = m_options.customAssistantColorButton->color(); newColor.setAlpha(m_selectedAssistant->assistantCustomColor().alpha()); m_selectedAssistant->setAssistantCustomColor(newColor); m_selectedAssistant->uncache(); } updateToolOptionsUI(); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotCustomOpacityChanged() { KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { QColor newColor = m_selectedAssistant->assistantCustomColor(); qreal newOpacity = m_options.customColorOpacitySlider->value() * 0.01 * 255.0; newColor.setAlpha(int(newOpacity)); m_selectedAssistant->setAssistantCustomColor(newColor); m_selectedAssistant->uncache(); } // this forces the canvas to refresh to see the changes immediately m_canvas->paintingAssistantsDecoration()->uncache(); m_canvas->canvasWidget()->update(); } diff --git a/plugins/assistants/Assistants/kis_assistant_tool.h b/plugins/assistants/Assistants/kis_assistant_tool.h index 26f3e506c8..0ef7512325 100644 --- a/plugins/assistants/Assistants/kis_assistant_tool.h +++ b/plugins/assistants/Assistants/kis_assistant_tool.h @@ -1,181 +1,176 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2017 Scott Petrovic * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_ASSISTANT_TOOL_H_ #define _KIS_ASSISTANT_TOOL_H_ #include #include #include #include #include "kis_painting_assistant.h" #include #include #include "ui_AssistantsToolOptions.h" /* The assistant tool allows artists to create special guides on the canvas * to help them with things like perspective and parallel lines * This tool has its own canvas decoration on it that only appears when the tool * is active. This decoration allows people to edit assistant points as well as delete assistants * Many of the operations here are forwarded on to another class (kis_painting_assistant_decoration) * that stores the assistant information as well as the decoration information with lines * * Drawing in two separate classes creates an issue where the editor controls in this class * are covered by the kis_painting_assistant_decoration class. In the future, we probably need to * do all the drawing in one class so we have better control of what is in front */ class KisAssistantTool : public KisTool { Q_OBJECT enum PerspectiveAssistantEditionMode { MODE_CREATION, // This is the mode when there is not yet a perspective grid MODE_EDITING, // This is the mode when the grid has been created, and we are waiting for the user to click on a control box MODE_DRAGGING_NODE, // In this mode one node is translated MODE_DRAGGING_TRANSLATING_TWONODES // This mode is used when creating a new sub perspective grid }; public: KisAssistantTool(KoCanvasBase * canvas); ~KisAssistantTool() override; virtual quint32 priority() { return 3; } /* this is a very big function that has to figure out if we are adding a new assistant, * or editing an existing one when we click on the canvas. There is also a lot of logic * in here that is specific to certain assistants and how they should be handled. * The editor widget is not a UI file, so the move, delete, preview areas have manual * hitbox regions specified to know if a click is doing any of those actions. */ void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; QWidget *createOptionWidget() override; - /// clones the list of assistants - /// the originally shared handles will still be shared - /// the cloned assistants do not share any handle with the original assistants - QList cloneAssistantList(const QList &list) const; - private: // adds and removes assistant. // this is event is forwarded to the kis_painting_decoration class // perspective grids seem to be managed in two places with these calls void addAssistant(); void removeAssistant(KisPaintingAssistantSP assistant); void assistantSelected(KisPaintingAssistantSP assistant); public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; void slotChangeVanishingPointAngle(double value); private Q_SLOTS: void removeAllAssistants(); void saveAssistants(); void loadAssistants(); void updateToolOptionsUI(); /// send the color and opacity information from the UI to the kis_painting_decoration /// which manages the assistants void slotGlobalAssistantsColorChanged(const QColor&); void slotGlobalAssistantOpacityChanged(); void slotUpdateCustomColor(); void slotCustomOpacityChanged(); protected: /// Draws the editor widget controls with move, activate, and delete /// This also creates a lot of assistant specific stuff for vanishing points and perspective grids /// Whatever is painted here will be underneath the content painted in the kis_painting_assistant_decoration /// The kis_painting_assistant_decoration paints the final assistant, so this is more of just editor controls void paint(QPainter& gc, const KoViewConverter &converter) override; protected: /// this class manipulates the kis_painting_assistant_decorations a lot, so this class is a helper /// to get a reference to it and call "updateCanvas" which refreshes the display QPointer m_canvas; /// the handles are retrieved from the kis_painting_decoration originally /// They are used here to generate and manipulate editor handles with the tool's primary action QList m_handles; QList m_sideHandles; KisPaintingAssistantHandleSP m_handleDrag; KisPaintingAssistantHandleSP m_handleCombine; KisPaintingAssistantSP m_assistantDrag; /// Used while a new assistant is being created. Most assistants need multiple points to exist /// so this helps manage the visual state while this creation process is going on KisPaintingAssistantSP m_newAssistant; QPointF m_cursorStart; QPointF m_currentAdjustment; Ui::AssistantsToolOptions m_options; QWidget* m_optionsWidget; QPointF m_dragStart; QLineF m_radius; bool m_snapIsRadial; QPointF m_dragEnd; int m_handleSize; // how large the editor handles will appear private: void drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& _gc); PerspectiveAssistantEditionMode m_internalMode; KisPaintingAssistantHandleSP m_selectedNode1, m_selectedNode2, m_higlightedNode; int m_assistantHelperYOffset; // used by the assistant editor icons for placement on the canvas. QList m_origAssistantList; }; class KisAssistantToolFactory : public KoToolFactoryBase { public: KisAssistantToolFactory() : KoToolFactoryBase("KisAssistantTool") { setToolTip(i18n("Assistant Tool")); setSection(TOOL_TYPE_VIEW); setIconName(koIconNameCStr("krita_tool_assistant")); setPriority(0); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisAssistantToolFactory() override {} KoToolBase * createTool(KoCanvasBase * canvas) override { return new KisAssistantTool(canvas); } }; #endif diff --git a/plugins/dockers/CMakeLists.txt b/plugins/dockers/CMakeLists.txt index 6952b67468..add898a795 100644 --- a/plugins/dockers/CMakeLists.txt +++ b/plugins/dockers/CMakeLists.txt @@ -1,35 +1,36 @@ add_subdirectory(layerdocker) if(HAVE_OPENEXR) add_subdirectory(smallcolorselector) endif() add_subdirectory(specificcolorselector) add_subdirectory(digitalmixer) add_subdirectory(advancedcolorselector) add_subdirectory(presetdocker) add_subdirectory(historydocker) add_subdirectory(channeldocker) add_subdirectory(artisticcolorselector) add_subdirectory(tasksetdocker) add_subdirectory(compositiondocker) add_subdirectory(patterndocker) add_subdirectory(griddocker) add_subdirectory(arrangedocker) if(HAVE_OCIO) add_subdirectory(lut) endif() add_subdirectory(overview) add_subdirectory(palettedocker) add_subdirectory(animation) add_subdirectory(presethistory) add_subdirectory(svgcollectiondocker) add_subdirectory(histogram) add_subdirectory(gamutmask) if(NOT APPLE AND HAVE_QT_QUICK) add_subdirectory(touchdocker) option(ENABLE_CPU_THROTTLE "Build the CPU Throttle Docker" OFF) if (ENABLE_CPU_THROTTLE) add_subdirectory(throttle) endif() endif() add_subdirectory(logdocker) +add_subdirectory(snapshotdocker) \ No newline at end of file diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index 10c82a7de5..722026f156 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,384 +1,402 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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 "palettedocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgpalettedock.h" PaletteDockerDock::PaletteDockerDock( ) : QDockWidget(i18n("Palette")) , m_ui(new Ui_WdgPaletteDock()) , m_model(new KisPaletteModel(this)) , m_paletteChooser(new KisPaletteListWidget(this)) , m_view(0) , m_resourceProvider(0) , m_rServer(KoResourceServerProvider::instance()->paletteServer()) , m_activeDocument(0) , m_paletteEditor(new KisPaletteEditor) , m_actAdd(new QAction(KisIconUtils::loadIcon("list-add"), i18n("Add a color"))) , m_actRemove(new QAction(KisIconUtils::loadIcon("edit-delete"), i18n("Delete color"))) , m_actModify(new QAction(KisIconUtils::loadIcon("edit-rename"), i18n("Modify this spot"))) , m_actEditPalette(new QAction(KisIconUtils::loadIcon("groupLayer"), i18n("Edit this palette"))) , m_colorSelfUpdate(false) { QWidget *mainWidget = new QWidget(this); setWidget(mainWidget); m_ui->setupUi(mainWidget); m_ui->bnAdd->setDefaultAction(m_actAdd.data()); m_ui->bnRemove->setDefaultAction(m_actRemove.data()); m_ui->bnRename->setDefaultAction(m_actModify.data()); m_ui->bnEditPalette->setDefaultAction(m_actEditPalette.data()); // to make sure their icons have the same size m_ui->bnRemove->setIconSize(QSize(16, 16)); m_ui->bnRename->setIconSize(QSize(16, 16)); m_ui->bnAdd->setIconSize(QSize(16, 16)); m_ui->bnEditPalette->setIconSize(QSize(16, 16)); m_ui->paletteView->setPaletteModel(m_model); m_ui->paletteView->setAllowModification(true); m_ui->cmbNameList->setCompanionView(m_ui->paletteView); m_paletteEditor->setPaletteModel(m_model); connect(m_actAdd.data(), SIGNAL(triggered()), SLOT(slotAddColor())); connect(m_actRemove.data(), SIGNAL(triggered()), SLOT(slotRemoveColor())); connect(m_actModify.data(), SIGNAL(triggered()), SLOT(slotEditEntry())); connect(m_actEditPalette.data(), SIGNAL(triggered()), SLOT(slotEditPalette())); connect(m_ui->paletteView, SIGNAL(sigIndexSelected(QModelIndex)), SLOT(slotPaletteIndexSelected(QModelIndex))); connect(m_ui->paletteView, SIGNAL(clicked(QModelIndex)), SLOT(slotPaletteIndexClicked(QModelIndex))); connect(m_ui->paletteView, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotPaletteIndexDoubleClicked(QModelIndex))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(const KoColor&)), SLOT(slotNameListSelection(const KoColor&))); m_viewContextMenu.addAction(m_actModify.data()); m_viewContextMenu.addAction(m_actRemove.data()); connect(m_ui->paletteView, SIGNAL(pressed(QModelIndex)), SLOT(slotContextMenu(QModelIndex))); m_paletteChooser->setAllowModification(true); connect(m_paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), SLOT(slotSetColorSet(KoColorSet*))); connect(m_paletteChooser, SIGNAL(sigAddPalette()), SLOT(slotAddPalette())); connect(m_paletteChooser, SIGNAL(sigImportPalette()), SLOT(slotImportPalette())); connect(m_paletteChooser, SIGNAL(sigRemovePalette(KoColorSet*)), SLOT(slotRemovePalette(KoColorSet*))); connect(m_paletteChooser, SIGNAL(sigExportPalette(KoColorSet*)), SLOT(slotExportPalette(KoColorSet*))); m_ui->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->bnColorSets->setToolTip(i18n("Choose palette")); m_ui->bnColorSets->setPopupWidget(m_paletteChooser); KisConfig cfg(true); QString defaultPaletteName = cfg.defaultPalette(); KoColorSet* defaultPalette = m_rServer->resourceByName(defaultPaletteName); if (defaultPalette) { slotSetColorSet(defaultPalette); } else { m_ui->bnAdd->setEnabled(false); m_ui->bnRename->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnEditPalette->setEnabled(false); m_ui->paletteView->setAllowModification(false); } } PaletteDockerDock::~PaletteDockerDock() { } void PaletteDockerDock::setViewManager(KisViewManager* kisview) { m_view = kisview; m_resourceProvider = kisview->canvasResourceProvider(); connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResource*)), SLOT(saveToWorkspace(KisWorkspaceResource*))); connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResource*)), SLOT(loadFromWorkspace(KisWorkspaceResource*))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), this, SLOT(slotFGColorResourceChanged(KoColor))); kisview->nodeManager()->disconnect(m_model); } void PaletteDockerDock::slotContextMenu(const QModelIndex &) { if (QApplication::mouseButtons() == Qt::RightButton) { m_viewContextMenu.exec(QCursor::pos()); } } void PaletteDockerDock::slotAddPalette() { m_paletteEditor->addPalette(); } void PaletteDockerDock::slotRemovePalette(KoColorSet *cs) { m_paletteEditor->removePalette(cs); } void PaletteDockerDock::slotImportPalette() { m_paletteEditor->importPalette(); } void PaletteDockerDock::slotExportPalette(KoColorSet *palette) { KoFileDialog dialog(this, KoFileDialog::SaveFile, "Save Palette"); dialog.setDefaultDir(palette->filename()); dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset"); QString newPath; bool isStandAlone = palette->isGlobal(); QString oriPath = palette->filename(); if ((newPath = dialog.filename()).isEmpty()) { return; } palette->setFilename(newPath); palette->setIsGlobal(true); palette->save(); palette->setFilename(oriPath); palette->setIsGlobal(isStandAlone); } void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (canvas) { KisCanvas2 *cv = qobject_cast(canvas); m_ui->paletteView->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); } if (m_activeDocument) { + m_connections.clear(); for (KoColorSet * &cs : m_activeDocument->paletteList()) { KoColorSet *tmpAddr = cs; cs = new KoColorSet(*cs); m_rServer->removeResourceFromServer(tmpAddr); } } if (m_view && m_view->document()) { m_activeDocument = m_view->document(); m_paletteEditor->setView(m_view); for (KoColorSet *cs : m_activeDocument->paletteList()) { m_rServer->addResource(cs); } + m_connections.addConnection(m_activeDocument, &KisDocument::sigPaletteListChanged, + this, &PaletteDockerDock::slotUpdatePaletteList); } if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::unsetCanvas() { setEnabled(false); m_ui->paletteView->setDisplayRenderer(0); m_paletteEditor->setView(0); for (KoResource *r : m_rServer->resources()) { KoColorSet *c = static_cast(r); if (!c->isGlobal()) { m_rServer->removeResourceFromServer(c); } } if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::slotSetColorSet(KoColorSet* colorSet) { if (colorSet && colorSet->isEditable()) { m_ui->bnAdd->setEnabled(true); m_ui->bnRename->setEnabled(true); m_ui->bnRemove->setEnabled(true); m_ui->bnEditPalette->setEnabled(true); m_ui->paletteView->setAllowModification(true); } else { m_ui->bnAdd->setEnabled(false); m_ui->bnRename->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnEditPalette->setEnabled(false); m_ui->paletteView->setAllowModification(false); } m_currentColorSet = colorSet; m_model->setPalette(colorSet); if (colorSet) { KisConfig cfg(true); cfg.setDefaultPalette(colorSet->name()); m_ui->lblPaletteName->setTextElideMode(Qt::ElideLeft); m_ui->lblPaletteName->setText(colorSet->name()); } else { m_ui->lblPaletteName->setText(""); } } void PaletteDockerDock::slotEditPalette() { KisDlgPaletteEditor dlg; if (!m_currentColorSet) { return; } dlg.setPaletteModel(m_model); dlg.setView(m_view); if (dlg.exec() != QDialog::Accepted){ return; } slotSetColorSet(m_currentColorSet); // update GUI } void PaletteDockerDock::slotAddColor() { if (m_resourceProvider) { m_paletteEditor->addEntry(m_resourceProvider->fgColor()); } } void PaletteDockerDock::slotRemoveColor() { QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } m_paletteEditor->removeEntry(index); m_ui->bnRemove->setEnabled(false); } void PaletteDockerDock::setFGColorByPalette(const KisSwatch &entry) { if (m_resourceProvider) { m_colorSelfUpdate = true; m_resourceProvider->setFGColor(entry.color()); m_colorSelfUpdate = false; } } void PaletteDockerDock::saveToWorkspace(KisWorkspaceResource* workspace) { if (!m_currentColorSet.isNull()) { workspace->setProperty("palette", m_currentColorSet->name()); } } void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResource* workspace) { if (workspace->hasProperty("palette")) { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorSet = rServer->resourceByName(workspace->getString("palette")); if (colorSet) { slotSetColorSet(colorSet); } } } void PaletteDockerDock::slotFGColorResourceChanged(const KoColor &color) { if (!m_colorSelfUpdate) { m_ui->paletteView->slotFGColorChanged(color); } } void PaletteDockerDock::slotPaletteIndexSelected(const QModelIndex &index) { bool occupied = qvariant_cast(index.data(KisPaletteModel::CheckSlotRole)); if (occupied) { if (!qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { m_ui->bnRemove->setEnabled(true); KisSwatch entry = m_model->getEntry(index); setFGColorByPalette(entry); } } if (!m_currentColorSet->isEditable()) { return; } m_ui->bnRemove->setEnabled(occupied); } void PaletteDockerDock::slotPaletteIndexClicked(const QModelIndex &index) { if (!(qvariant_cast(index.data(KisPaletteModel::CheckSlotRole)))) { setEntryByForeground(index); } } void PaletteDockerDock::slotPaletteIndexDoubleClicked(const QModelIndex &index) { m_paletteEditor->modifyEntry(index); } void PaletteDockerDock::setEntryByForeground(const QModelIndex &index) { m_paletteEditor->setEntry(m_resourceProvider->fgColor(), index); if (m_currentColorSet->isEditable()) { m_ui->bnRemove->setEnabled(true); } } void PaletteDockerDock::slotEditEntry() { QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } m_paletteEditor->modifyEntry(index); } void PaletteDockerDock::slotNameListSelection(const KoColor &color) { m_colorSelfUpdate = true; m_ui->paletteView->selectClosestColor(color); m_resourceProvider->setFGColor(color); m_colorSelfUpdate = false; } + +void PaletteDockerDock::slotUpdatePaletteList(const QList &oldPaletteList, const QList &newPaletteList) +{ + for (KoColorSet *cs : oldPaletteList) { + m_rServer->removeResourceFromServer(cs); + } + + for (KoColorSet *cs : newPaletteList) { + m_rServer->addResource(cs); + } + + if (!m_currentColorSet) { + slotSetColorSet(0); + } +} diff --git a/plugins/dockers/palettedocker/palettedocker_dock.h b/plugins/dockers/palettedocker/palettedocker_dock.h index 3776d58bbe..fff55179a2 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.h +++ b/plugins/dockers/palettedocker/palettedocker_dock.h @@ -1,115 +1,119 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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 PALETTEDOCKER_DOCK_H #define PALETTEDOCKER_DOCK_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include class KisViewManager; class KisCanvasResourceProvider; class KisWorkspaceResource; class KisPaletteListWidget; class KisPaletteModel; class KisPaletteEditor; class Ui_WdgPaletteDock; class PaletteDockerDock : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: PaletteDockerDock(); ~PaletteDockerDock() override; public: // QDockWidget void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; public: // KisMainWindowObserver void setViewManager(KisViewManager* kisview) override; private Q_SLOTS: void slotContextMenu(const QModelIndex &); void slotAddPalette(); void slotRemovePalette(KoColorSet *); void slotImportPalette(); void slotExportPalette(KoColorSet *); void slotAddColor(); void slotRemoveColor(); void slotEditEntry(); void slotEditPalette(); void slotPaletteIndexSelected(const QModelIndex &index); void slotPaletteIndexClicked(const QModelIndex &index); void slotPaletteIndexDoubleClicked(const QModelIndex &index); void slotNameListSelection(const KoColor &color); void slotSetColorSet(KoColorSet* colorSet); void saveToWorkspace(KisWorkspaceResource* workspace); void loadFromWorkspace(KisWorkspaceResource* workspace); void slotFGColorResourceChanged(const KoColor& color); + void slotUpdatePaletteList(const QList &oldPaletteList, const QList &newPaletteList); private: void setEntryByForeground(const QModelIndex &index); void setFGColorByPalette(const KisSwatch &entry); private /* member variables */: QScopedPointer m_ui; KisPaletteModel *m_model; KisPaletteListWidget *m_paletteChooser; QPointer m_view; KisCanvasResourceProvider *m_resourceProvider; KoResourceServer * const m_rServer; QPointer m_activeDocument; QPointer m_currentColorSet; QScopedPointer m_paletteEditor; QScopedPointer m_actAdd; QScopedPointer m_actRemove; QScopedPointer m_actModify; QScopedPointer m_actEditPalette; QMenu m_viewContextMenu; bool m_colorSelfUpdate; + + KisSignalAutoConnectionsStore m_connections; }; #endif diff --git a/plugins/dockers/snapshotdocker/CMakeLists.txt b/plugins/dockers/snapshotdocker/CMakeLists.txt new file mode 100644 index 0000000000..0d3c5ba7fa --- /dev/null +++ b/plugins/dockers/snapshotdocker/CMakeLists.txt @@ -0,0 +1,10 @@ +set(kritasnapshotdocker_SOURCES + KisSnapshotModel.cpp + SnapshotDocker.cpp + SnapshotPlugin.cpp + KisSnapshotView.cpp + ) + +add_library(kritasnapshotdocker MODULE ${kritasnapshotdocker_SOURCES}) +target_link_libraries(kritasnapshotdocker kritaimage kritaui) +install(TARGETS kritasnapshotdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) \ No newline at end of file diff --git a/plugins/dockers/snapshotdocker/KisSnapshotModel.cpp b/plugins/dockers/snapshotdocker/KisSnapshotModel.cpp new file mode 100644 index 0000000000..7ff4c7a6f7 --- /dev/null +++ b/plugins/dockers/snapshotdocker/KisSnapshotModel.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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 "KisSnapshotModel.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct KisSnapshotModel::Private +{ + Private(); + virtual ~Private(); + + QPointer curDocument(); + QScopedPointer curNameServer; + bool switchToDocument(QPointer doc); + + using DocPList = QList > >; + + DocPList curDocList; + + QMap documentGroups; + QMap nameServers; + QPointer curCanvas; +}; + +KisSnapshotModel::Private::Private() +{ +} + +KisSnapshotModel::Private::~Private() +{ +} + +QPointer KisSnapshotModel::Private::curDocument() +{ + if (curCanvas && curCanvas->imageView()) { + return curCanvas->imageView()->document(); + } + return 0; +} + +bool KisSnapshotModel::Private::switchToDocument(QPointer doc) +{ + if (curCanvas && curCanvas->imageView()) { + KisView *view = curCanvas->imageView(); + KisDocument *curDoc = curDocument(); + if (curDoc && doc) { + curDoc->copyFromDocument(*doc); + view->viewManager()->nodeManager()->slotNonUiActivatedNode(curDoc->preActivatedNode()); + } + // FIXME: more things need to be done + return true; + } + return false; +} + +KisSnapshotModel::KisSnapshotModel() + : QAbstractListModel() + , m_d(new Private) +{ +} + +KisSnapshotModel::~KisSnapshotModel() +{ +} + +int KisSnapshotModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } else { + return m_d->curDocList.size(); + } +} + +QVariant KisSnapshotModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= rowCount(QModelIndex())) { + return QVariant(); + } + int i = index.row(); + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return m_d->curDocList[i].first; + break; + } + return QVariant(); +} + +bool KisSnapshotModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() >= rowCount(QModelIndex())) { + return false; + } + int i = index.row(); + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + m_d->curDocList[i].first = value.toString(); + emit dataChanged(index, index); + return true; + break; + } + return false; +} + +Qt::ItemFlags KisSnapshotModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::ItemIsEnabled; + } + + return QAbstractListModel::flags(index) | Qt::ItemIsEditable; +} + +void KisSnapshotModel::setCanvas(QPointer canvas) +{ + if (m_d->curCanvas == canvas) { + return; + } + + if (m_d->curDocument()) { + m_d->documentGroups.insert(m_d->curDocument(), m_d->curDocList); + m_d->nameServers.insert(m_d->curDocument(), m_d->curNameServer.take()); + } else { + m_d->curNameServer.reset(0); + Q_FOREACH (auto const &i, m_d->curDocList) { + delete i.second.data(); + } + } + + if (!m_d->curDocList.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_d->curDocList.size() - 1); + m_d->curDocList.clear(); + endRemoveRows(); + } + m_d->curCanvas = canvas; + + QPointer curDoc = m_d->curDocument(); + if (curDoc) { + Private::DocPList docList = m_d->documentGroups.take(curDoc); + beginInsertRows(QModelIndex(), docList.size(), docList.size()); + m_d->curDocList = docList; + endInsertRows(); + + KisNameServer *nameServer = m_d->nameServers.take(curDoc); + if (!nameServer) { + nameServer = new KisNameServer; + } + m_d->curNameServer.reset(nameServer); + } + +} + +bool KisSnapshotModel::slotCreateSnapshot() +{ + if (!m_d->curDocument()) { + return false; + } + QPointer clonedDoc(m_d->curDocument()->lockAndCreateSnapshot()); + if (clonedDoc) { + beginInsertRows(QModelIndex(), m_d->curDocList.size(), m_d->curDocList.size()); + m_d->curDocList << qMakePair(i18nc("snapshot names, e.g. \"Snapshot 1\"", "Snapshot %1", m_d->curNameServer->number()), clonedDoc); + endInsertRows(); + return true; + } + return false; +} + +bool KisSnapshotModel::slotRemoveSnapshot(const QModelIndex &index) +{ + if (!index.isValid() || index.row() >= m_d->curDocList.size()) { + return false; + } + int i = index.row(); + beginRemoveRows(QModelIndex(), i, i); + QPair > pair = m_d->curDocList.takeAt(i); + endRemoveRows(); + delete pair.second.data(); + return true; +} + +bool KisSnapshotModel::slotSwitchToSnapshot(const QModelIndex &index) +{ + if (!index.isValid() || index.row() >= m_d->curDocList.size()) { + return false; + } + + return m_d->switchToDocument(m_d->curDocList[index.row()].second); +} diff --git a/plugins/dockers/snapshotdocker/KisSnapshotModel.h b/plugins/dockers/snapshotdocker/KisSnapshotModel.h new file mode 100644 index 0000000000..92addb1671 --- /dev/null +++ b/plugins/dockers/snapshotdocker/KisSnapshotModel.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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_SNAPSHOT_MODEL_H_ +#define KIS_SNAPSHOT_MODEL_H_ + +#include +#include +#include + +#include + +class KisSnapshotModel : public QAbstractListModel +{ +public: + KisSnapshotModel(); + ~KisSnapshotModel() override; + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + // this function is re-implemented to make the items editable + Qt::ItemFlags flags(const QModelIndex &index) const override; + void setCanvas(QPointer canvas); + +public Q_SLOTS: + bool slotCreateSnapshot(); + bool slotRemoveSnapshot(const QModelIndex &index); + bool slotSwitchToSnapshot(const QModelIndex &index); + +private: + struct Private; + QScopedPointer m_d; +}; + +#endif // KIS_SNAPSHOT_MODEL_H_ diff --git a/plugins/dockers/snapshotdocker/KisSnapshotView.cpp b/plugins/dockers/snapshotdocker/KisSnapshotView.cpp new file mode 100644 index 0000000000..657d2ca7c0 --- /dev/null +++ b/plugins/dockers/snapshotdocker/KisSnapshotView.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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 "KisSnapshotView.h" +#include "KisSnapshotModel.h" + +#include + +struct KisSnapshotView::Private +{ + KisSnapshotModel *model; +}; + +KisSnapshotView::KisSnapshotView() + : QListView() + , m_d(new Private) +{ + setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked); +} + +KisSnapshotView::~KisSnapshotView() +{ +} + +void KisSnapshotView::setModel(QAbstractItemModel *model) +{ + KisSnapshotModel *snapshotModel = dynamic_cast(model); + if (snapshotModel) { + QListView::setModel(model); + m_d->model = snapshotModel; + } +} + +void KisSnapshotView::slotSwitchToSelectedSnapshot() +{ + KIS_ASSERT_RECOVER_RETURN(m_d->model); + QModelIndexList indexes = selectedIndexes(); + if (indexes.size() != 1) { + return; + } + m_d->model->slotSwitchToSnapshot(indexes[0]); +} + +void KisSnapshotView::slotRemoveSelectedSnapshot() +{ + KIS_ASSERT_RECOVER_RETURN(m_d->model); + QModelIndexList indexes = selectedIndexes(); + Q_FOREACH (QModelIndex index, indexes) { + m_d->model->slotRemoveSnapshot(index); + } +} + diff --git a/plugins/impex/tiff/tests/kis_tiff_test.h b/plugins/dockers/snapshotdocker/KisSnapshotView.h similarity index 62% copy from plugins/impex/tiff/tests/kis_tiff_test.h copy to plugins/dockers/snapshotdocker/KisSnapshotView.h index 11505376f7..3539329acb 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.h +++ b/plugins/dockers/snapshotdocker/KisSnapshotView.h @@ -1,36 +1,40 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (c) 2019 Tusooa Zhu * * 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_TIFF_TEST_H_ -#define _KIS_TIFF_TEST_H_ +#ifndef KIS_SNAPSHOT_VIEW_H_ +#define KIS_SNAPSHOT_VIEW_H_ -#include +#include -class KisTiffTest : public QObject +class KisSnapshotView : public QListView { - Q_OBJECT -private Q_SLOTS: - void testFiles(); - void testRoundTripRGBF16(); +public: + KisSnapshotView(); + ~KisSnapshotView() override; - void testImportFromWriteonly(); - void testExportToReadonly(); - void testImportIncorrectFormat(); + void setModel(QAbstractItemModel *model) override; + +public Q_SLOTS: + void slotSwitchToSelectedSnapshot(); + void slotRemoveSelectedSnapshot(); +private: + struct Private; + QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/snapshotdocker/SnapshotDocker.cpp b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp new file mode 100644 index 0000000000..8b9218d826 --- /dev/null +++ b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Tusooa Zhu + * + * 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 "SnapshotDocker.h" + +#include +#include +#include +#include +#include + +#include "KisSnapshotModel.h" +#include "KisSnapshotView.h" + +#include +#include + +struct SnapshotDocker::Private +{ + Private(); + ~Private(); + + QScopedPointer model; + QPointer view; + QPointer canvas; + QPointer bnAdd; + QPointer bnSwitchTo; + QPointer bnRemove; +}; + +SnapshotDocker::Private::Private() + : model(new KisSnapshotModel) + , view(new KisSnapshotView) + , canvas(0) + , bnAdd(new QToolButton) + , bnSwitchTo(new QToolButton) + , bnRemove(new QToolButton) +{ +} + +SnapshotDocker::Private::~Private() +{ +} + +SnapshotDocker::SnapshotDocker() + : QDockWidget() + , m_d(new Private) +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *mainLayout = new QVBoxLayout(widget); + m_d->view->setModel(m_d->model.data()); + mainLayout->addWidget(m_d->view); + + QHBoxLayout *buttonsLayout = new QHBoxLayout(widget); + m_d->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); + connect(m_d->bnAdd, &QToolButton::clicked, m_d->model.data(), &KisSnapshotModel::slotCreateSnapshot); + buttonsLayout->addWidget(m_d->bnAdd); + m_d->bnSwitchTo->setIcon(KisIconUtils::loadIcon("draw-freehand")); /// XXX: which icon to use? + connect(m_d->bnSwitchTo, &QToolButton::clicked, m_d->view, &KisSnapshotView::slotSwitchToSelectedSnapshot); + buttonsLayout->addWidget(m_d->bnSwitchTo); + m_d->bnRemove->setIcon(KisIconUtils::loadIcon("deletelayer")); + connect(m_d->bnRemove, &QToolButton::clicked, m_d->view, &KisSnapshotView::slotRemoveSelectedSnapshot); + buttonsLayout->addWidget(m_d->bnRemove); + mainLayout->addLayout(buttonsLayout); + + setWidget(widget); + setWindowTitle(i18n("Snapshot Docker")); +} + +SnapshotDocker::~SnapshotDocker() +{ +} + +void SnapshotDocker::setCanvas(KoCanvasBase *canvas) +{ + KisCanvas2 *c = dynamic_cast(canvas); + if (c) { + if (m_d->canvas == c) { + return; + } + } + m_d->canvas = c; + m_d->model->setCanvas(c); +} + +void SnapshotDocker::unsetCanvas() +{ + setCanvas(0); +} + +#include "SnapshotDocker.moc" diff --git a/plugins/impex/kra/kra_export.h b/plugins/dockers/snapshotdocker/SnapshotDocker.h similarity index 55% copy from plugins/impex/kra/kra_export.h copy to plugins/dockers/snapshotdocker/SnapshotDocker.h index bffdb4f824..b3ccfe8b31 100644 --- a/plugins/impex/kra/kra_export.h +++ b/plugins/dockers/snapshotdocker/SnapshotDocker.h @@ -1,39 +1,47 @@ -/* - * Copyright (C) 2016 Boudewijn Rempt +/* This file is part of the KDE project + * Copyright (C) 2010 Matus Talcik * * 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. */ +#ifndef SNAPSHOT_DOCKER_H_ +#define SNAPSHOT_DOCKER_H_ -#ifndef _KRA_EXPORT_H_ -#define _KRA_EXPORT_H_ +#include +#include -#include +#include +#include -#include +#include +#include -class KraExport : public KisImportExportFilter +class SnapshotDocker : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT public: - KraExport(QObject *parent, const QVariantList &); - ~KraExport() override; -public: - KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; - void initializeCapabilities() override; + SnapshotDocker(); + ~SnapshotDocker() override; + + QString observerName() override { return "SnapshotDocker"; } + void setCanvas(KoCanvasBase *canvas) override; + void unsetCanvas() override; +private: + struct Private; + QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/snapshotdocker/SnapshotPlugin.cpp b/plugins/dockers/snapshotdocker/SnapshotPlugin.cpp new file mode 100644 index 0000000000..a7eae99da2 --- /dev/null +++ b/plugins/dockers/snapshotdocker/SnapshotPlugin.cpp @@ -0,0 +1,70 @@ +/* This file is part of the KDE project + * Copyright (C) 2010 Matus Talcik + * + * 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 "SnapshotPlugin.h" + + +#include +#include + +#include +#include + +#include "SnapshotDocker.h" + +K_PLUGIN_FACTORY_WITH_JSON(SnapshotPluginFactory, "kritasnapshotdocker.json", registerPlugin();) + +class SnapshotDockFactory : public KoDockFactoryBase +{ +public: + SnapshotDockFactory() { + } + + QString id() const override { + return QString("Snapshot"); + } + + virtual Qt::DockWidgetArea defaultDockWidgetArea() const { + return Qt::RightDockWidgetArea; + } + + QDockWidget *createDockWidget() override { + SnapshotDocker *dockWidget = new SnapshotDocker(); + dockWidget->setObjectName(id()); + + return dockWidget; + } + + DockPosition defaultDockPosition() const override { + return DockRight; + } +}; + + +SnapshotPlugin::SnapshotPlugin(QObject *parent, const QVariantList &) + : QObject(parent) +{ + + KoDockRegistry::instance()->add(new SnapshotDockFactory()); +} + +SnapshotPlugin::~SnapshotPlugin() +{ +} + +#include "SnapshotPlugin.moc" diff --git a/plugins/impex/kra/kra_export.h b/plugins/dockers/snapshotdocker/SnapshotPlugin.h similarity index 64% copy from plugins/impex/kra/kra_export.h copy to plugins/dockers/snapshotdocker/SnapshotPlugin.h index bffdb4f824..bd3d32c463 100644 --- a/plugins/impex/kra/kra_export.h +++ b/plugins/dockers/snapshotdocker/SnapshotPlugin.h @@ -1,39 +1,34 @@ -/* - * Copyright (C) 2016 Boudewijn Rempt +/* This file is part of the KDE project + * Copyright (C) 2010 Matus Talcik * * 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. */ -#ifndef _KRA_EXPORT_H_ -#define _KRA_EXPORT_H_ +#ifndef SNAPSHOT_PLUGIN_H_ +#define SNAPSHOT_PLUGIN_H_ +#include #include -#include - -class KraExport : public KisImportExportFilter +class SnapshotPlugin : public QObject { Q_OBJECT public: - KraExport(QObject *parent, const QVariantList &); - ~KraExport() override; -public: - KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; - void initializeCapabilities() override; - + SnapshotPlugin(QObject *parent, const QVariantList &); + ~SnapshotPlugin() override; }; #endif diff --git a/plugins/dockers/snapshotdocker/kritasnapshotdocker.json b/plugins/dockers/snapshotdocker/kritasnapshotdocker.json new file mode 100644 index 0000000000..c81539b4a0 --- /dev/null +++ b/plugins/dockers/snapshotdocker/kritasnapshotdocker.json @@ -0,0 +1,9 @@ +{ + "Id": "Krita Snapshot Docker plugin", + "Type": "Service", + "X-KDE-Library": "kritasnapshotdocker", + "X-KDE-ServiceTypes": [ + "Krita/Dock" + ], + "X-Krita-Version": "28" +} diff --git a/plugins/filters/palettize/CMakeLists.txt b/plugins/filters/palettize/CMakeLists.txt index 1573abce5b..3aa4ae992d 100644 --- a/plugins/filters/palettize/CMakeLists.txt +++ b/plugins/filters/palettize/CMakeLists.txt @@ -1,4 +1,5 @@ set(kritapalettize_SOURCES palettize.cpp) +ki18n_wrap_ui(kritapalettize_SOURCES palettize.ui) add_library(kritapalettize MODULE ${kritapalettize_SOURCES}) target_link_libraries(kritapalettize kritaui) install(TARGETS kritapalettize DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/filters/palettize/palettize.cpp b/plugins/filters/palettize/palettize.cpp index 4ef7a14c14..8ea2950cb2 100644 --- a/plugins/filters/palettize/palettize.cpp +++ b/plugins/filters/palettize/palettize.cpp @@ -1,276 +1,351 @@ /* * This file is part of Krita * * Copyright (c) 2019 Carl Olsson * * 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 "palettize.h" -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include +#include #include -#include -#include #include -#include -#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include K_PLUGIN_FACTORY_WITH_JSON(PalettizeFactory, "kritapalettize.json", registerPlugin();) Palettize::Palettize(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterPalettize()); } +#include "palettize.moc" + KisFilterPalettize::KisFilterPalettize() : KisFilter(id(), FiltersCategoryMapId, i18n("&Palettize...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); setShowConfigurationWidget(true); } KisPalettizeWidget::KisPalettizeWidget(QWidget* parent) : KisConfigWidget(parent) { - QGridLayout* layout = new QGridLayout(this); - layout->setColumnStretch(0, 0); - layout->setColumnStretch(1, 1); - - KisElidedLabel* paletteLabel = new KisElidedLabel(i18n("Palette"), Qt::ElideRight, this); - layout->addWidget(paletteLabel, 0, 0); - KisIconWidget* paletteIcon = new KisIconWidget(this); - paletteIcon->setFixedSize(32, 32); + setupUi(this); + + paletteIconWidget->setFixedSize(32, 32); KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); QSharedPointer paletteAdapter(new KoResourceServerAdapter(paletteServer)); m_paletteWidget = new KoResourceItemChooser(paletteAdapter, this, false); - paletteIcon->setPopupWidget(m_paletteWidget); - QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, paletteIcon, &KisIconWidget::setResource); + paletteIconWidget->setPopupWidget(m_paletteWidget); + QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, paletteIconWidget, &KisIconWidget::setResource); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, this, &KisConfigWidget::sigConfigurationItemChanged); - paletteLabel->setBuddy(paletteIcon); - layout->addWidget(paletteIcon, 0, 1, Qt::AlignLeft); - - m_ditherGroupBox = new QGroupBox(i18n("Dither"), this); - m_ditherGroupBox->setCheckable(true); - QGridLayout* ditherLayout = new QGridLayout(m_ditherGroupBox); - QObject::connect(m_ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); - ditherLayout->setColumnStretch(0, 0); - ditherLayout->setColumnStretch(1, 1); - layout->addWidget(m_ditherGroupBox, 1, 0, 1, 2); - - QRadioButton* ditherPatternRadio = new QRadioButton(i18n("Pattern"), this); - ditherLayout->addWidget(ditherPatternRadio, 0, 0); - KisIconWidget* ditherPatternIcon = new KisIconWidget(this); - ditherPatternIcon->setFixedSize(32, 32); + + QObject::connect(colorspaceComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); + + QObject::connect(ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); + + patternIconWidget->setFixedSize(32, 32); KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); QSharedPointer patternAdapter(new KoResourceServerAdapter(patternServer)); m_ditherPatternWidget = new KoResourceItemChooser(patternAdapter, this, false); - ditherPatternIcon->setPopupWidget(m_ditherPatternWidget); - QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, ditherPatternIcon, &KisIconWidget::setResource); + patternIconWidget->setPopupWidget(m_ditherPatternWidget); + QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, patternIconWidget, &KisIconWidget::setResource); QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, this, &KisConfigWidget::sigConfigurationItemChanged); - ditherLayout->addWidget(ditherPatternIcon, 0, 1, Qt::AlignLeft); - m_ditherPatternUseAlphaCheckBox = new QCheckBox(i18n("Use alpha"), this); - QObject::connect(m_ditherPatternUseAlphaCheckBox, &QCheckBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); - ditherLayout->addWidget(m_ditherPatternUseAlphaCheckBox, 0, 2, Qt::AlignLeft); - - QRadioButton* ditherNoiseRadio = new QRadioButton(i18n("Noise"), this); - ditherLayout->addWidget(ditherNoiseRadio, 1, 0); - m_ditherNoiseSeedWidget = new QLineEdit(this); - m_ditherNoiseSeedWidget->setValidator(new QIntValidator(this)); - QObject::connect(m_ditherNoiseSeedWidget, &QLineEdit::textChanged, this, &KisConfigWidget::sigConfigurationItemChanged); - ditherLayout->addWidget(m_ditherNoiseSeedWidget, 1, 1, 1, 2); - - KisElidedLabel* ditherWeightLabel = new KisElidedLabel(i18n("Weight"), Qt::ElideRight, this); - ditherLayout->addWidget(ditherWeightLabel, 2, 0); - m_ditherWeightWidget = new KisDoubleWidget(this); - m_ditherWeightWidget->setRange(0.0, 1.0); - m_ditherWeightWidget->setSingleStep(0.0625); - m_ditherWeightWidget->setPageStep(0.25); - QObject::connect(m_ditherWeightWidget, &KisDoubleWidget::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); - ditherWeightLabel->setBuddy(m_ditherWeightWidget); - ditherLayout->addWidget(m_ditherWeightWidget, 2, 1, 1, 2); - - m_ditherModeGroup = new QButtonGroup(this); - m_ditherModeGroup->addButton(ditherPatternRadio, 0); - m_ditherModeGroup->addButton(ditherNoiseRadio, 1); - QObject::connect(m_ditherModeGroup, QOverload::of(&QButtonGroup::buttonClicked), this, &KisConfigWidget::sigConfigurationItemChanged); - - layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Preferred, QSizePolicy::Expanding), 2, 0, 1, 2); -} -void KisPalettizeWidget::setConfiguration(const KisPropertiesConfigurationSP config) -{ - KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); - if (palette) m_paletteWidget->setCurrentResource(palette); + QObject::connect(patternValueModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); + + noiseSeedLineEdit->setValidator(new QIntValidator(this)); + QObject::connect(noiseSeedLineEdit, &QLineEdit::textChanged, this, &KisConfigWidget::sigConfigurationItemChanged); - m_ditherGroupBox->setChecked(config->getBool("ditherEnabled")); + QObject::connect(noiseSeedRandomizeButton, &QToolButton::clicked, [this](){ + noiseSeedLineEdit->setText(QString::number(rand())); + }); - QAbstractButton* ditherModeButton = m_ditherModeGroup->button(config->getInt("ditherMode")); - if (ditherModeButton) ditherModeButton->setChecked(true); + QObject::connect(thresholdModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); - KoPattern* ditherPattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(config->getString("ditherPattern")); - if (ditherPattern) m_ditherPatternWidget->setCurrentResource(ditherPattern); + QObject::connect(colorModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); - m_ditherPatternUseAlphaCheckBox->setChecked(config->getBool("ditherPatternUseAlpha")); + offsetScaleSpinBox->setPrefix(QString("%1 ").arg(i18n("Offset Scale:"))); + offsetScaleSpinBox->setRange(0.0, 1.0, 3); + offsetScaleSpinBox->setSingleStep(0.125); + QObject::connect(offsetScaleSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); - m_ditherNoiseSeedWidget->setText(QString::number(config->getInt("ditherNoiseSeed"))); + spreadSpinBox->setPrefix(QString("%1 ").arg(i18n("Spread:"))); + spreadSpinBox->setRange(0.0, 1.0, 3); + spreadSpinBox->setSingleStep(0.125); + QObject::connect(spreadSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); - m_ditherWeightWidget->setValue(config->getDouble("ditherWeight")); + QObject::connect(alphaGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); + + QObject::connect(alphaModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); + + alphaClipSpinBox->setPrefix(QString("%1 ").arg(i18n("Clip:"))); + alphaClipSpinBox->setRange(0.0, 1.0, 3); + alphaClipSpinBox->setSingleStep(0.125); + QObject::connect(alphaClipSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); + + alphaIndexSpinBox->setPrefix(QString("%1 ").arg(i18n("Index:"))); + alphaIndexSpinBox->setRange(0, 255); + QObject::connect(alphaIndexSpinBox, &KisSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); + QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, [this](){ + const KoColorSet* const palette = static_cast(m_paletteWidget->currentResource()); + alphaIndexSpinBox->setMaximum(palette ? int(palette->colorCount() - 1) : 0); + alphaIndexSpinBox->setValue(std::min(alphaIndexSpinBox->value(), alphaIndexSpinBox->maximum())); + }); +} + +void KisPalettizeWidget::setConfiguration(const KisPropertiesConfigurationSP config) +{ + KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); + if (palette) m_paletteWidget->setCurrentResource(palette); + colorspaceComboBox->setCurrentIndex(config->getInt("colorspace")); + ditherGroupBox->setChecked(config->getBool("ditherEnabled")); + thresholdModeComboBox->setCurrentIndex(config->getInt("thresholdMode")); + KoPattern* pattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(config->getString("pattern")); + if (pattern) m_ditherPatternWidget->setCurrentResource(pattern); + patternValueModeComboBox->setCurrentIndex(config->getInt("patternValueMode")); + noiseSeedLineEdit->setText(QString::number(config->getInt("noiseSeed"))); + colorModeComboBox->setCurrentIndex(config->getInt("colorMode")); + offsetScaleSpinBox->setValue(config->getDouble("offsetScale")); + spreadSpinBox->setValue(config->getDouble("spread")); + alphaGroupBox->setChecked(config->getBool("alphaEnabled")); + alphaModeComboBox->setCurrentIndex(config->getInt("alphaMode")); + alphaClipSpinBox->setValue(config->getDouble("alphaClip")); + alphaIndexSpinBox->setValue(config->getInt("alphaIndex")); } KisPropertiesConfigurationSP KisPalettizeWidget::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1); + if (m_paletteWidget->currentResource()) config->setProperty("palette", QVariant(m_paletteWidget->currentResource()->name())); - config->setProperty("ditherEnabled", m_ditherGroupBox->isChecked()); - config->setProperty("ditherMode", m_ditherModeGroup->checkedId()); - if (m_ditherPatternWidget->currentResource()) config->setProperty("ditherPattern", QVariant(m_ditherPatternWidget->currentResource()->name())); - config->setProperty("ditherPatternUseAlpha", m_ditherPatternUseAlphaCheckBox->isChecked()); - config->setProperty("ditherNoiseSeed", m_ditherNoiseSeedWidget->text().toInt()); - config->setProperty("ditherWeight", m_ditherWeightWidget->value()); + config->setProperty("colorspace", colorspaceComboBox->currentIndex()); + config->setProperty("ditherEnabled", ditherGroupBox->isChecked()); + config->setProperty("thresholdMode",thresholdModeComboBox->currentIndex()); + if (m_ditherPatternWidget->currentResource()) config->setProperty("pattern", QVariant(m_ditherPatternWidget->currentResource()->name())); + config->setProperty("patternValueMode", patternValueModeComboBox->currentIndex()); + config->setProperty("noiseSeed", noiseSeedLineEdit->text().toInt()); + config->setProperty("colorMode", colorModeComboBox->currentIndex()); + config->setProperty("offsetScale", offsetScaleSpinBox->value()); + config->setProperty("spread", spreadSpinBox->value()); + config->setProperty("alphaEnabled", alphaGroupBox->isChecked()); + config->setProperty("alphaMode", alphaModeComboBox->currentIndex()); + config->setProperty("alphaClip", alphaClipSpinBox->value()); + config->setProperty("alphaIndex", alphaIndexSpinBox->value()); return config; } KisConfigWidget* KisFilterPalettize::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const { Q_UNUSED(dev) Q_UNUSED(useForMasks) return new KisPalettizeWidget(parent); } KisFilterConfigurationSP KisFilterPalettize::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1); + config->setProperty("palette", "Default"); + config->setProperty("colorspace", Colorspace::Lab); config->setProperty("ditherEnabled", false); - config->setProperty("ditherMode", DitherMode::Pattern); - config->setProperty("ditherPattern", "Grid01.pat"); - config->setProperty("ditherPatternUseAlpha", true); - config->setProperty("ditherNoiseSeed", rand()); - config->setProperty("ditherWeight", 1.0); + config->setProperty("thresholdMode", ThresholdMode::Pattern); + config->setProperty("pattern", "DITH 0202 GEN "); + config->setProperty("patternValueMode", PatternValueMode::Auto); + config->setProperty("noiseSeed", rand()); + config->setProperty("colorMode", ColorMode::PerChannelOffset); + config->setProperty("offsetScale", 0.125); + config->setProperty("spread", 1.0); + config->setProperty("alphaEnabled", true); + config->setProperty("alphaMode", AlphaMode::Clip); + config->setProperty("alphaClip", 0.5); + config->setProperty("alphaIndex", 0); return config; } void KisFilterPalettize::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { const KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); + const int searchColorspace = config->getInt("colorspace"); const bool ditherEnabled = config->getBool("ditherEnabled"); - const int ditherMode = config->getInt("ditherMode"); - const KoPattern* ditherPattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(config->getString("ditherPattern")); - const bool ditherPatternUseAlpha = config->getBool("ditherPatternUseAlpha"); - const quint64 ditherNoiseSeed = quint64(config->getInt("ditherNoiseSeed")); - const double ditherWeight = config->getDouble("ditherWeight"); - - const KoColorSpace* cs = device->colorSpace(); - KisRandomGenerator random(ditherNoiseSeed); - - using TreeColor = boost::geometry::model::point; - using TreeValue = std::pair>; - using Rtree = boost::geometry::index::rtree>; - Rtree m_rtree; + const int thresholdMode = config->getInt("thresholdMode"); + const KoPattern* pattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(config->getString("pattern")); + const int patternValueMode = config->getInt("patternValueMode"); + const quint64 noiseSeed = quint64(config->getInt("noiseSeed")); + const int colorMode = config->getInt("colorMode"); + const double offsetScale = config->getDouble("offsetScale"); + const double spread = config->getDouble("spread"); + const bool alphaEnabled = config->getBool("alphaEnabled"); + const int alphaMode = config->getInt("alphaMode"); + const double alphaClip = config->getDouble("alphaClip"); + const int alphaIndex = config->getInt("alphaIndex"); + + const KoColorSpace* colorspace = device->colorSpace(); + const KoColorSpace* workColorspace = (searchColorspace == Colorspace::Lab + ? KoColorSpaceRegistry::instance()->lab16() + : KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc")); + + const quint8 colorCount = ditherEnabled && colorMode == ColorMode::NearestColors ? 2 : 1; + + KisRandomGenerator random(noiseSeed); + + using SearchColor = boost::geometry::model::point; + struct ColorCandidate { + KoColor color; + quint16 index; + double distance; + }; + using SearchEntry = std::pair; + boost::geometry::index::rtree> rtree; if (palette) { + // Add palette colors to search tree quint16 index = 0; for (int row = 0; row < palette->rowCount(); ++row) { for (int column = 0; column < palette->columnCount(); ++column) { KisSwatch swatch = palette->getColorGlobal(column, row); if (swatch.isValid()) { - KoColor color = swatch.color().convertedTo(cs); - TreeColor searchColor; - KoColor tempColor; - cs->toLabA16(color.data(), tempColor.data(), 1); - memcpy(reinterpret_cast(&searchColor), tempColor.data(), sizeof(TreeColor)); + KoColor color = swatch.color().convertedTo(colorspace); + KoColor workColor = swatch.color().convertedTo(workColorspace); + SearchColor searchColor; + memcpy(&searchColor, workColor.data(), sizeof(SearchColor)); // Don't add duplicates so won't dither between identical colors - std::vector result; - m_rtree.query(boost::geometry::index::contains(searchColor), std::back_inserter(result)); - if (result.empty()) m_rtree.insert(std::make_pair(searchColor, std::make_pair(color, index++))); + std::vector result; + rtree.query(boost::geometry::index::contains(searchColor), std::back_inserter(result)); + if (result.empty()) rtree.insert(SearchEntry(searchColor, {color, index, 0.0})); } + ++index; } } - } - KisSequentialIteratorProgress it(device, applyRect, progressUpdater); - while (it.nextPixel()) { - // Find 2 nearest palette colors to pixel color - TreeColor imageColor; - KoColor tempColor; - cs->toLabA16(it.oldRawData(), tempColor.data(), 1); - memcpy(reinterpret_cast(&imageColor), tempColor.data(), sizeof(TreeColor)); - std::vector nearestColors; - nearestColors.reserve(2); - for (Rtree::const_query_iterator it = m_rtree.qbegin(boost::geometry::index::nearest(imageColor, 2)); it != m_rtree.qend(); ++it) { - nearestColors.push_back(*it); + // Automatically pick between lightness-based and alpha-based patterns by whichever has maximum range + bool patternUseAlpha; + if (pattern && ditherEnabled && thresholdMode == ThresholdMode::Pattern && patternValueMode == PatternValueMode::Auto) { + qreal lightnessMin = 1.0, lightnessMax = 0.0; + qreal alphaMin = 1.0, alphaMax = 0.0; + const QImage &image = pattern->pattern(); + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); ++x) { + const QColor pixel = image.pixelColor(x, y); + lightnessMin = std::min(lightnessMin, pixel.lightnessF()); + lightnessMax = std::max(lightnessMax, pixel.lightnessF()); + alphaMin = std::min(alphaMin, pixel.alphaF()); + alphaMax = std::max(alphaMax, pixel.alphaF()); + } + } + patternUseAlpha = (alphaMax - alphaMin > lightnessMax - lightnessMin); + } + else { + patternUseAlpha = (patternValueMode == PatternValueMode::Alpha); } - if (nearestColors.size() > 0) { - size_t nearestIndex; - // Dither not enabled or only one color found so don't dither - if (!ditherEnabled || nearestColors.size() == 1) nearestIndex = 0; - // Otherwise threshold between colors based on relative distance + KisSequentialIteratorProgress pixel(device, applyRect, progressUpdater); + while (pixel.nextPixel()) { + KoColor workColor(pixel.oldRawData(), colorspace); + workColor.convertTo(workColorspace); + + // Find dither threshold + double ditherPos = 0.5; + if (pattern && ditherEnabled) { + if (thresholdMode == ThresholdMode::Pattern) { + const QImage &image = pattern->pattern(); + const QColor ditherPixel = image.pixelColor(pixel.x() % image.width(), pixel.y() % image.height()); + ditherPos = (patternUseAlpha ? ditherPixel.alphaF() : ditherPixel.lightnessF()); + } + else if (thresholdMode == ThresholdMode::Noise) { + ditherPos = random.doubleRandomAt(pixel.x(), pixel.y()); + } + + // Traditional per-channel ordered dithering + if (colorMode == ColorMode::PerChannelOffset) { + QVector normalized(int(workColorspace->channelCount())); + workColorspace->normalisedChannelsValue(workColor.data(), normalized); + for (int channel = 0; channel < int(workColorspace->channelCount()); ++channel) { + normalized[channel] += (ditherPos - 0.5) * offsetScale; + } + workColorspace->fromNormalisedChannelsValue(workColor.data(), normalized); + } + } + const double ditherThreshold = (0.5 - (spread / 2.0) + ditherPos * spread); + + // Get candidate colors and their distances + SearchColor searchColor; + memcpy(reinterpret_cast(&searchColor), workColor.data(), sizeof(SearchColor)); + std::vector candidateColors; + candidateColors.reserve(size_t(colorCount)); + double distanceSum = 0.0; + for (auto it = rtree.qbegin(boost::geometry::index::nearest(searchColor, colorCount)); it != rtree.qend() && candidateColors.size() < colorCount; ++it) { + ColorCandidate candidate = it->second; + candidate.distance = boost::geometry::distance(searchColor, it->first); + candidateColors.push_back(candidate); + distanceSum += candidate.distance; + } + + // Select color candidate + quint16 selected; + if (ditherEnabled && colorMode == ColorMode::NearestColors) { + // Sort candidates by palette order for stable dither color ordering + const bool swap = candidateColors[0].index > candidateColors[1].index; + selected = swap ^ (candidateColors[swap].distance / distanceSum > ditherThreshold); + } else { - std::vector distances(nearestColors.size()); - double distanceSum = 0.0; - for (size_t i = 0; i < nearestColors.size(); ++i) { - distances[i] = boost::geometry::distance(imageColor, nearestColors[i].first); - distanceSum += distances[i]; + selected = 0; + } + ColorCandidate &candidate = candidateColors[selected]; + + // Set alpha + const double oldAlpha = colorspace->opacityF(pixel.oldRawData()); + double newAlpha = oldAlpha; + if (alphaEnabled && !(!ditherEnabled && alphaMode == AlphaMode::UseDither)) { + if (alphaMode == AlphaMode::Clip) { + newAlpha = oldAlpha < alphaClip? 0.0 : 1.0; } - // Use palette ordering for stable dither color threshold ordering - size_t ditherIndices[2] = {0, 1}; - if (nearestColors[ditherIndices[0]].second.second > nearestColors[ditherIndices[1]].second.second) std::swap(ditherIndices[0], ditherIndices[1]); - const double pos = distances[ditherIndices[0]] / distanceSum; - double threshold = 0.5; - if (ditherMode == DitherMode::Pattern) { - const QImage &image = ditherPattern->pattern(); - const QColor pixel = image.pixelColor(it.x() % image.width(), it.y() % image.height()); - threshold = ditherPatternUseAlpha ? pixel.alphaF() : pixel.lightnessF(); + else if (alphaMode == AlphaMode::Index) { + newAlpha = (candidate.index == alphaIndex ? 0.0 : 1.0); } - else if (ditherMode == DitherMode::Noise) { - threshold = random.doubleRandomAt(it.x(), it.y()); + else if (alphaMode == AlphaMode::UseDither) { + if (colorMode == ColorMode::PerChannelOffset) { + newAlpha = colorspace->opacityF(workColor.convertedTo(colorspace).data()) < 0.5 ? 0.0 : 1.0; + } + else if (colorMode == ColorMode::NearestColors) { + newAlpha = oldAlpha < ditherThreshold ? 0.0 : 1.0; + } } - nearestIndex = pos < (0.5 - (ditherWeight / 2.0) + threshold * ditherWeight) ? ditherIndices[0] : ditherIndices[1]; } - memcpy(it.rawData(), nearestColors[nearestIndex].second.first.data(), cs->pixelSize()); + colorspace->setOpacity(candidate.color.data(), newAlpha, 1); + + // Copy color to pixel + memcpy(pixel.rawData(), candidate.color.data(), colorspace->pixelSize()); } } } - -#include "palettize.moc" diff --git a/plugins/filters/palettize/palettize.h b/plugins/filters/palettize/palettize.h index c4db9ab6e5..53b8cb95d5 100644 --- a/plugins/filters/palettize/palettize.h +++ b/plugins/filters/palettize/palettize.h @@ -1,77 +1,85 @@ /* * This file is part of the KDE project * * Copyright (c) 2019 Carl Olsson * * 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 PALETTIZE_H #define PALETTIZE_H +#include "ui_palettize.h" + #include #include #include #include #include #include #include -class KoColorSet; class KoResourceItemChooser; -class QGroupBox; -class KoPattern; -class QCheckBox; -class QLineEdit; -class QButtonGroup; -class KisDoubleWidget; class Palettize : public QObject { public: Palettize(QObject *parent, const QVariantList &); }; -class KisPalettizeWidget : public KisConfigWidget +class KisPalettizeWidget : public KisConfigWidget, public Ui::Palettize { public: KisPalettizeWidget(QWidget* parent = 0); void setConfiguration(const KisPropertiesConfigurationSP) override; KisPropertiesConfigurationSP configuration() const override; private: KoResourceItemChooser* m_paletteWidget; - QGroupBox* m_ditherGroupBox; - QButtonGroup* m_ditherModeGroup; KoResourceItemChooser* m_ditherPatternWidget; - QCheckBox* m_ditherPatternUseAlphaCheckBox; - QLineEdit* m_ditherNoiseSeedWidget; - KisDoubleWidget* m_ditherWeightWidget; }; class KisFilterPalettize : public KisFilter { public: - enum DitherMode { + enum Colorspace { + Lab, + RGB + }; + enum AlphaMode { + Clip, + Index, + UseDither + }; + enum ThresholdMode { Pattern, Noise }; + enum PatternValueMode { + Auto, + Lightness, + Alpha + }; + enum ColorMode { + PerChannelOffset, + NearestColors + }; KisFilterPalettize(); static inline KoID id() { return KoID("palettize", i18n("Palettize")); } KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; KisFilterConfigurationSP factoryConfiguration() const override; void processImpl(KisPaintDeviceSP device, const QRect &applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override; }; #endif diff --git a/plugins/filters/palettize/palettize.ui b/plugins/filters/palettize/palettize.ui new file mode 100644 index 0000000000..ffdc646182 --- /dev/null +++ b/plugins/filters/palettize/palettize.ui @@ -0,0 +1,467 @@ + + + Palettize + + + + 0 + 0 + 286 + 468 + + + + Palettize + + + + + + + 0 + 0 + + + + Palette + + + paletteIconWidget + + + + + + + ... + + + + + + + Colorspace + + + colorspaceComboBox + + + + + + + + Lab + + + + + RGB + + + + + + + + Dither + + + true + + + + + + + 0 + 0 + + + + Threshold Mode + + + thresholdModeComboBox + + + + + + + + Pattern + + + + + Noise + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + 1 + + + Amount: + + + + + + + + 0 + 0 + + + + Pattern + + + patternIconWidget + + + + + + + ... + + + + + + + Value Mode + + + patternValueModeComboBox + + + + + + + + Auto + + + + + Lightness + + + + + Alpha + + + + + + + + + + + + Seed + + + noiseSeedLineEdit + + + + + + + + + + Randomize + + + Qt::ToolButtonTextOnly + + + + + + + + + + + + 0 + 0 + + + + Color Mode + + + colorModeComboBox + + + + + + + + Per Channel Offset + + + + + Nearest Colors + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + Alpha + + + true + + + + + + + 0 + 0 + + + + Alpha Mode + + + alphaModeComboBox + + + + + + + + Clip + + + + + Index + + + + + Use Dither + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + 0 + + + Amount: + + + + + + + + + + + + + + + + + + + + + + + + + + KisIconWidget + QToolButton +
kis_iconwidget.h
+
+ + KisDoubleSliderSpinBox + QDoubleSpinBox +
kis_slider_spin_box.h
+
+ + KisSliderSpinBox + QSpinBox +
kis_slider_spin_box.h
+
+
+ + + + thresholdModeComboBox + currentIndexChanged(int) + thresholdModeStackedWidget + setCurrentIndex(int) + + + 264 + 163 + + + 229 + 173 + + + + + thresholdModeStackedWidget + currentChanged(int) + thresholdModeComboBox + setCurrentIndex(int) + + + 264 + 246 + + + 264 + 163 + + + + + colorModeComboBox + currentIndexChanged(int) + colorModeStackedWidget + setCurrentIndex(int) + + + 264 + 278 + + + 264 + 331 + + + + + colorModeStackedWidget + currentChanged(int) + colorModeComboBox + setCurrentIndex(int) + + + 264 + 331 + + + 264 + 278 + + + + + alphaModeComboBox + currentIndexChanged(int) + alphaModeStackedWidget + setCurrentIndex(int) + + + 168 + 392 + + + 162 + 422 + + + + + alphaModeStackedWidget + currentChanged(int) + alphaModeComboBox + setCurrentIndex(int) + + + 86 + 422 + + + 127 + 398 + + + + +
diff --git a/plugins/impex/brush/CMakeLists.txt b/plugins/impex/brush/CMakeLists.txt index 3f44e9e589..b0343eddd4 100644 --- a/plugins/impex/brush/CMakeLists.txt +++ b/plugins/impex/brush/CMakeLists.txt @@ -1,28 +1,29 @@ add_subdirectory(tests) set(kritabrushexport_PART_SRCS kis_brush_export.cpp + KisWdgOptionsBrush.cpp KisAnimatedBrushAnnotation.cpp ) ki18n_wrap_ui(kritabrushexport_PART_SRCS wdg_export_gih.ui) add_library(kritabrushexport MODULE ${kritabrushexport_PART_SRCS}) target_link_libraries(kritabrushexport kritalibbrush kritalibpaintop kritaui kritaimpex) install(TARGETS kritabrushexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritabrushimport_PART_SRCS kis_brush_import.cpp KisAnimatedBrushAnnotation.cpp ) ki18n_wrap_ui(kritabrushimport_PART_SRCS ) add_library(kritabrushimport MODULE ${kritabrushimport_PART_SRCS}) target_link_libraries(kritabrushimport kritalibbrush kritaui) install(TARGETS kritabrushimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_brush.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/brush/KisWdgOptionsBrush.cpp b/plugins/impex/brush/KisWdgOptionsBrush.cpp new file mode 100644 index 0000000000..4b0e8d75c7 --- /dev/null +++ b/plugins/impex/brush/KisWdgOptionsBrush.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2016 Boudewijn Rempt + * Copyright (c) 2019 Iván SantaMaría + * + * 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 "KisWdgOptionsBrush.h" + +#include +#include +#include +#include + +KisWdgOptionsBrush::KisWdgOptionsBrush(QWidget *parent) + : KisConfigWidget(parent) + , m_currentDimensions(0) + , m_layersCount(0) + , m_view(0) +{ + setupUi(this); + connect(this->brushStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotEnableSelectionMethod(int))); + connect(this->dimensionSpin, SIGNAL(valueChanged(int)), SLOT(slotActivateDimensionRanks())); + + slotEnableSelectionMethod(brushStyle->currentIndex()); + + BrushPipeSelectionModeHelper *bp; + for (int i = 0; i < this->dimensionSpin->maximum(); i++) { + bp = new BrushPipeSelectionModeHelper(0, i); + connect(bp, SIGNAL(sigRankChanged(int)), SLOT(slotRecalculateRanks(int))); + dimRankLayout->addWidget(bp); + } + + slotActivateDimensionRanks(); +} + +void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg) +{ + spacingWidget->setSpacing(false, cfg->getDouble("spacing")); + if (nameLineEdit->text().isEmpty()) { + nameLineEdit->setText(cfg->getString("name")); + } + colorAsMask->setChecked(cfg->getBool("mask")); + brushStyle->setCurrentIndex(cfg->getInt("brushStyle")); + dimensionSpin->setValue(cfg->getInt("dimensions")); + + QLayoutItem *item; + BrushPipeSelectionModeHelper *bp; + for (int i = 0; i < dimensionSpin->maximum(); ++i) { + if ((item = dimRankLayout->itemAt(i)) != 0) { + bp = dynamic_cast(item->widget()); + bp->cmbSelectionMode.setCurrentIndex(cfg->getInt("selectionMode" + QString::number(i))); + bp->rankSpinBox.setValue(cfg->getInt("rank" + QString::number(i))); + } + } +} + +KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const +{ + KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); + cfg->setProperty("spacing", spacingWidget->spacing()); + cfg->setProperty("name", nameLineEdit->text()); + cfg->setProperty("mask", colorAsMask->isChecked()); + cfg->setProperty("brushStyle", brushStyle->currentIndex()); + cfg->setProperty("dimensions", dimensionSpin->value()); + + QLayoutItem *item; + BrushPipeSelectionModeHelper *bp; + for (int i = 0; i < dimensionSpin->maximum(); ++i) { + if ((item = dimRankLayout->itemAt(i)) != 0) { + bp = dynamic_cast(item->widget()); + cfg->setProperty("selectionMode" + QString::number(i), bp->cmbSelectionMode.currentIndex()); + cfg->setProperty("rank" + QString::number(i), bp->rankSpinBox.value()); + } + } + + return cfg; +} + +void KisWdgOptionsBrush::setView(KisViewManager *view) +{ + if (view) { + m_view = view; + KoProperties properties; + properties.setProperty("visible", true); + m_layersCount = m_view->image()->root()->childNodes(QStringList("KisLayer"), properties).count(); + + slotRecalculateRanks(); + } +} + +void KisWdgOptionsBrush::slotEnableSelectionMethod(int value) +{ + if (value == 0) { + animStyleGroup->setEnabled(false); + } else { + animStyleGroup->setEnabled(true); + } +} + +void KisWdgOptionsBrush::slotActivateDimensionRanks() +{ + QLayoutItem *item; + BrushPipeSelectionModeHelper *bp; + int dim = this->dimensionSpin->value(); + if (dim >= m_currentDimensions) { + for (int i = m_currentDimensions; i < dim; ++i) { + if ((item = dimRankLayout->itemAt(i)) != 0) { + bp = dynamic_cast(item->widget()); + bp->setEnabled(true); + bp->show(); + } + } + } + else { + for (int i = m_currentDimensions -1; i >= dim; --i) { + if ((item = dimRankLayout->itemAt(i)) != 0) { + bp = dynamic_cast(item->widget()); + bp->setEnabled(false); + bp->hide(); + } + } + } + m_currentDimensions = dim; +} + +void KisWdgOptionsBrush::slotRecalculateRanks(int rankDimension) +{ + int rankSum = 0; + int maxDim = this->dimensionSpin->maximum(); + + QVector bp; + QLayoutItem *item; + + for (int i = 0; i < maxDim; ++i) { + if ((item = dimRankLayout->itemAt(i)) != 0) { + bp.push_back(dynamic_cast(item->widget())); + rankSum += bp.at(i)->rankSpinBox.value(); + } + } + + BrushPipeSelectionModeHelper *currentBrushHelper; + BrushPipeSelectionModeHelper *callerBrushHelper = bp.at(rankDimension); + QVectorIterator bpIterator(bp); + + while (rankSum > m_layersCount && bpIterator.hasNext()) { + currentBrushHelper = bpIterator.next(); + + if (currentBrushHelper != callerBrushHelper) { + int currentValue = currentBrushHelper->rankSpinBox.value(); + currentBrushHelper->rankSpinBox.setValue(currentValue -1); + rankSum -= currentValue; + } + } + + if (rankSum > m_layersCount) { + callerBrushHelper->rankSpinBox.setValue(m_layersCount); + } + + if (rankSum == 0) { + bp.at(0)->rankSpinBox.setValue(m_layersCount); + return; + } +} diff --git a/plugins/impex/brush/KisWdgOptionsBrush.h b/plugins/impex/brush/KisWdgOptionsBrush.h new file mode 100644 index 0000000000..fbcf95eb60 --- /dev/null +++ b/plugins/impex/brush/KisWdgOptionsBrush.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2016 Boudewijn Rempt + * Copyright (c) 2019 Iván SantaMaría + * + * 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 KISWDGOPTIONSBRUSH_H +#define KISWDGOPTIONSBRUSH_H + +#include +#include +#include + +#include +#include +#include +#include + +class BrushPipeSelectionModeHelper : public QWidget +{ + Q_OBJECT + +public: + BrushPipeSelectionModeHelper(QWidget *parent, int dimension) + : QWidget(parent) + , cmbSelectionMode(this) + , rankSpinBox(this) + , rankLbl(this) + , horizLayout(this) + , dimension(dimension) + { + cmbSelectionMode.addItem(i18n("Constant")); + cmbSelectionMode.addItem(i18n("Random")); + cmbSelectionMode.addItem(i18n("Incremental")); + cmbSelectionMode.addItem(i18n("Pressure")); + cmbSelectionMode.addItem(i18n("Angular")); + cmbSelectionMode.addItem(i18n("Velocity")); + + horizLayout.setSpacing(6); + horizLayout.setMargin(0); + + QSizePolicy sizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); + sizePolicy.setHorizontalStretch(1); + sizePolicy.setVerticalStretch(0); + + this->setSizePolicy(sizePolicy); + + cmbSelectionMode.setSizePolicy(sizePolicy); + cmbSelectionMode.setCurrentIndex(2); + + rankSpinBox.setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred)); + rankLbl.setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); + + rankLbl.setText(i18n("Rank")); + horizLayout.addWidget(&rankLbl); + horizLayout.addWidget(&rankSpinBox); + horizLayout.addWidget(&cmbSelectionMode); + + connect(&rankSpinBox, SIGNAL(valueChanged(int)), this, SLOT(slotRankChanged())); + + this->hide(); + this->setEnabled(false); + } + + QComboBox cmbSelectionMode; + QSpinBox rankSpinBox; + QLabel rankLbl; + QHBoxLayout horizLayout; + + int dimension; + + +Q_SIGNALS: + void sigRankChanged(int rankEmitter); + +public Q_SLOTS: + void slotRankChanged() + { + emit sigRankChanged(dimension); + } + +}; + +class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih +{ + Q_OBJECT + +public: + KisWdgOptionsBrush(QWidget *parent); + + void setConfiguration(const KisPropertiesConfigurationSP cfg) override; + KisPropertiesConfigurationSP configuration() const override; + + void setView(KisViewManager *view) override; + +public Q_SLOTS: + + void slotEnableSelectionMethod(int value); + void slotActivateDimensionRanks(); + void slotRecalculateRanks(int rankDimension = 0); + +private: + int m_currentDimensions; + int m_layersCount; + KisViewManager *m_view; +}; + +#endif // KISWDGOPTIONSBRUSH_H diff --git a/plugins/impex/brush/kis_brush_export.cpp b/plugins/impex/brush/kis_brush_export.cpp index db595ff7f4..981ac59956 100644 --- a/plugins/impex/brush/kis_brush_export.cpp +++ b/plugins/impex/brush/kis_brush_export.cpp @@ -1,229 +1,252 @@ /* * Copyright (c) 2016 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 "kis_brush_export.h" #include #include #include #include #include #include #include #include #include +#include +#include +#include #include #include #include #include #include #include #include #include +#include #include #include struct KisBrushExportOptions { qreal spacing; bool mask; int brushStyle; - int selectionMode; + int dimensions; + qint32 ranks[KisPipeBrushParasite::MaxDim]; + qint32 selectionModes[KisPipeBrushParasite::MaxDim]; QString name; }; K_PLUGIN_FACTORY_WITH_JSON(KisBrushExportFactory, "krita_brush_export.json", registerPlugin();) KisBrushExport::KisBrushExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushExport::~KisBrushExport() { } KisImportExportErrorCode KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { // XXX: Loading the parasite itself was commented out -- needs investigation // KisAnnotationSP annotation = document->savingImage()->annotation("ImagePipe Parasite"); // KisPipeBrushParasite parasite; // if (annotation) { // QBuffer buf(const_cast(&annotation->annotation())); // buf.open(QBuffer::ReadOnly); // parasite.loadFromDevice(&buf); // buf.close(); // } KisBrushExportOptions exportOptions; + if (document->savingImage()->dynamicPropertyNames().contains("brushspacing")) { exportOptions.spacing = document->savingImage()->property("brushspacing").toFloat(); } else { exportOptions.spacing = configuration->getInt("spacing"); } if (!configuration->getString("name").isEmpty()) { exportOptions.name = configuration->getString("name"); } else { exportOptions.name = document->savingImage()->objectName(); } + exportOptions.mask = configuration->getBool("mask"); - exportOptions.selectionMode = configuration->getInt("selectionMode"); exportOptions.brushStyle = configuration->getInt("brushStyle"); + exportOptions.dimensions = configuration->getInt("dimensions"); + + for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { + exportOptions.selectionModes[i] = configuration->getInt("selectionMode" + QString::number(i)); + exportOptions.ranks[i] = configuration->getInt("rank" + QString::number(i)); + } KisGbrBrush *brush = 0; if (mimeType() == "image/x-gimp-brush") { brush = new KisGbrBrush(filename()); } else if (mimeType() == "image/x-gimp-brush-animated") { brush = new KisImagePipeBrush(filename()); } else { return ImportExportCodes::FileFormatIncorrect; } qApp->processEvents(); // For vector layers to be updated QRect rc = document->savingImage()->bounds(); - brush->setName(exportOptions.name); brush->setSpacing(exportOptions.spacing); - brush->setUseColorAsMask(exportOptions.mask); - - - KisImagePipeBrush *pipeBrush = dynamic_cast(brush); if (pipeBrush) { // Create parasite. XXX: share with KisCustomBrushWidget QVector< QVector > devices; devices.push_back(QVector()); KoProperties properties; properties.setProperty("visible", true); QList layers = document->savingImage()->root()->childNodes(QStringList("KisLayer"), properties); Q_FOREACH (KisNodeSP node, layers) { - devices[0].push_back(node->projection().data()); + // push_front to behave exactly as gimp for gih creation + devices[0].push_front(node->projection().data()); } QVector modes; - switch (exportOptions.selectionMode) { - case 0: modes.push_back(KisParasite::Constant); break; - case 1: modes.push_back(KisParasite::Random); break; - case 2: modes.push_back(KisParasite::Incremental); break; - case 3: modes.push_back(KisParasite::Pressure); break; - case 4: modes.push_back(KisParasite::Angular); break; - case 5: modes.push_back(KisParasite::Velocity); break; - default: modes.push_back(KisParasite::Incremental); + + for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { + switch (exportOptions.selectionModes[i]) { + case 0: modes.push_back(KisParasite::Constant); break; + case 1: modes.push_back(KisParasite::Random); break; + case 2: modes.push_back(KisParasite::Incremental); break; + case 3: modes.push_back(KisParasite::Pressure); break; + case 4: modes.push_back(KisParasite::Angular); break; + case 5: modes.push_back(KisParasite::Velocity); break; + default: modes.push_back(KisParasite::Incremental); + } } KisPipeBrushParasite parasite; - // XXX: share code with KisImagePipeBrush, when we figure out how to support more gih features - parasite.dim = devices.count(); - // XXX Change for multidim! : + parasite.dim = exportOptions.dimensions; parasite.ncells = devices.at(0).count(); - parasite.rank[0] = parasite.ncells; // ### This can mask some bugs, be careful here in the future - parasite.selection[0] = modes.at(0); + + int maxRanks = 0; + for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { + // ### This can mask some bugs, be careful here in the future + parasite.rank[i] = exportOptions.ranks[i]; + parasite.selection[i] = modes.at(i); + maxRanks += exportOptions.ranks[i]; + } + + if (maxRanks > layers.count()) { + return ImportExportCodes::FileFormatIncorrect; + } // XXX needs movement! parasite.setBrushesCount(); pipeBrush->setParasite(parasite); pipeBrush->setDevices(devices, rc.width(), rc.height()); + + if (exportOptions.mask) { + QVector brushes = pipeBrush->brushes(); + Q_FOREACH(KisGbrBrush* brush, brushes) { + brush->setHasColor(false); + } + } } else { - QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - image.save("~/bla.png"); - brush->setImage(image); - brush->setBrushTipImage(image); + if (exportOptions.mask) { + QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + brush->setImage(image); + brush->setBrushTipImage(image); + } else { + brush->initFromPaintDev(document->savingImage()->projection(),0,0,rc.width(), rc.height()); + } } + brush->setName(exportOptions.name); + // brushes are created after devices are loaded, call mask mode after that + brush->setUseColorAsMask(exportOptions.mask); brush->setWidth(rc.width()); brush->setHeight(rc.height()); if (brush->saveToDevice(io)) { return ImportExportCodes::OK; } else { return ImportExportCodes::Failure; } } KisPropertiesConfigurationSP KisBrushExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", 1.0); cfg->setProperty("name", ""); cfg->setProperty("mask", true); - cfg->setProperty("selectionMode", 0); cfg->setProperty("brushStyle", 0); + cfg->setProperty("dimensions", 1); + + for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { + cfg->setProperty("selectionMode" + QString::number(i), 2); + cfg->getInt("rank" + QString::number(i), 0); + } return cfg; } KisConfigWidget *KisBrushExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &to) const { KisWdgOptionsBrush *wdg = new KisWdgOptionsBrush(parent); if (to == "image/x-gimp-brush") { wdg->groupBox->setVisible(false); + wdg->animStyleGroup->setVisible(false); } else if (to == "image/x-gimp-brush-animated") { wdg->groupBox->setVisible(true); + wdg->animStyleGroup->setVisible(true); } + + // preload gih name with chosen filename + QFileInfo fileLocation(filename()); + wdg->nameLineEdit->setText(fileLocation.baseName()); return wdg; } void KisBrushExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Gimp Brushes"); if (mimeType() == "image/x-gimp-brush-animated") { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); } } -void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg) -{ - spacingWidget->setSpacing(false, cfg->getDouble("spacing")); - nameLineEdit->setText(cfg->getString("name")); - colorAsMask->setChecked(cfg->getBool("mask")); - brushStyle->setCurrentIndex(cfg->getInt("brushStyle")); - cmbSelectionMode->setCurrentIndex(cfg->getInt("selectionMode")); -} - -KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const -{ - KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); - cfg->setProperty("spacing", spacingWidget->spacing()); - cfg->setProperty("name", nameLineEdit->text()); - cfg->setProperty("mask", colorAsMask->isChecked()); - cfg->setProperty("selectionMode", cmbSelectionMode->currentIndex()); - cfg->setProperty("brushStyle", brushStyle->currentIndex()); - return cfg; -} - - #include "kis_brush_export.moc" diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h index 681c343ef7..14eeed9d6a 100644 --- a/plugins/impex/brush/kis_brush_export.h +++ b/plugins/impex/brush/kis_brush_export.h @@ -1,68 +1,45 @@ /* * Copyright (c) 2016 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 _KIS_Brush_EXPORT_H_ #define _KIS_Brush_EXPORT_H_ #include +#include +#include #include -#include #include #include -class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih -{ - Q_OBJECT - -public: - KisWdgOptionsBrush(QWidget *parent) - : KisConfigWidget(parent) - { - setupUi(this); - connect(this->brushStyle, SIGNAL(currentIndexChanged(int)), SLOT(enableSelectionMedthod(int))); - } - - void setConfiguration(const KisPropertiesConfigurationSP cfg) override; - KisPropertiesConfigurationSP configuration() const override; -public Q_SLOTS: - void enableSelectionMedthod(int value) { - if (value == 0) { - cmbSelectionMode->setEnabled(false); - } else { - cmbSelectionMode->setEnabled(true); - } - } -}; - class KisBrushExport : public KisImportExportFilter { Q_OBJECT public: KisBrushExport(QObject *parent, const QVariantList &); ~KisBrushExport() override; KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/brush/wdg_export_gih.ui b/plugins/impex/brush/wdg_export_gih.ui index 8bb205a09a..1aeea206a1 100644 --- a/plugins/impex/brush/wdg_export_gih.ui +++ b/plugins/impex/brush/wdg_export_gih.ui @@ -1,207 +1,277 @@ WdgExportGih 0 0 468 - 318 + 429 Spacing: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Create mask from color 0 - 110 + 0 Brush Style false - - + + + 9 + + 3 - + 0 0 Style: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Regular Animated - - - - - 0 - 0 - - - - Selection mode: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - false - - - - 0 - 0 - - - - 2 - - - - Constant - - + + + + + + + + + Animated style options + + + + 9 + + + 9 + + + 9 + + + 9 + + + 0 + + + + + 3 + + + QLayout::SetDefaultConstraint + + + - - Random - + + + + 0 + 0 + + + + Dimensions + + + 0 + + - - Incremental - + + + + 0 + 0 + + + + 1 + + + 4 + + - - Pressure - + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + 6 + - - Angular - + + + + 0 + 0 + + + + Selection mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + - - Velocity - + + + Qt::Horizontal + + + + 20 + 20 + + + - + + + + + 3 + + + + + 6 + Qt::Vertical 20 40 KisSpacingSelectionWidget QWidget
kis_spacing_selection_widget.h
1
diff --git a/plugins/impex/kra/kra_export.cpp b/plugins/impex/kra/kra_export.cpp index 4e9c4d8a7e..5db4f6535d 100644 --- a/plugins/impex/kra/kra_export.cpp +++ b/plugins/impex/kra/kra_export.cpp @@ -1,78 +1,93 @@ /* * Copyright (C) 2016 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 "kra_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kra_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_kra_export.json", registerPlugin();) KraExport::KraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KraExport::~KraExport() { } KisImportExportErrorCode KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); KraConverter kraConverter(document); KisImportExportErrorCode res = kraConverter.buildFile(io, filename()); dbgFile << "KraExport::convert result =" << res; return res; } void KraExport::initializeCapabilities() { // Kra supports everything, by definition KisExportCheckFactory *factory = 0; Q_FOREACH(const QString &id, KisExportCheckRegistry::instance()->keys()) { factory = KisExportCheckRegistry::instance()->get(id); addCapability(factory->create(KisExportCheckBase::SUPPORTED)); } } +QString KraExport::verify(const QString &fileName) const +{ + QString error = KisImportExportFilter::verify(fileName); + if (error.isEmpty()) { + return KisImportExportFilter::verifyZiPBasedFiles(fileName, + QStringList() + << "mimetype" + << "documentinfo.xml" + << "maindoc.xml" + << "preview.png"); + } + return error; +} + + #include diff --git a/plugins/impex/kra/kra_export.h b/plugins/impex/kra/kra_export.h index bffdb4f824..77e5a7823f 100644 --- a/plugins/impex/kra/kra_export.h +++ b/plugins/impex/kra/kra_export.h @@ -1,39 +1,39 @@ /* * Copyright (C) 2016 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. */ #ifndef _KRA_EXPORT_H_ #define _KRA_EXPORT_H_ #include #include class KraExport : public KisImportExportFilter { Q_OBJECT public: KraExport(QObject *parent, const QVariantList &); ~KraExport() override; public: KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; - + QString verify(const QString &fileName) const override; }; #endif diff --git a/plugins/impex/ora/ora_export.cc b/plugins/impex/ora/ora_export.cc index 711dbbbe79..e352f63005 100644 --- a/plugins/impex/ora/ora_export.cc +++ b/plugins/impex/ora/ora_export.cc @@ -1,103 +1,117 @@ /* * Copyright (c) 2007 Cyrille Berger * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_export.h" #include #include #include #include #include - +#include #include #include #include #include #include #include #include #include #include #include #include #include "ora_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_ora_export.json", registerPlugin();) OraExport::OraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraExport::~OraExport() { } bool hasShapeLayerChild(KisNodeSP node) { if (!node) return false; Q_FOREACH (KisNodeSP child, node->childNodes(QStringList(), KoProperties())) { if (child->inherits("KisShapeLayer") || child->inherits("KisGeneratorLayer") || child->inherits("KisCloneLayer")) { return true; } else { if (hasShapeLayerChild(child)) { return true; } } } return false; } KisImportExportErrorCode OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); OraConverter oraConverter(document); KisImportExportErrorCode res = oraConverter.buildFile(io, image, {document->preActivatedNode()}); return res; } void OraExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisAdjustmentLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "OpenRaster"); } +QString OraExport::verify(const QString &fileName) const +{ + QString error = KisImportExportFilter::verify(fileName); + if (error.isEmpty()) { + return KisImportExportFilter::verifyZiPBasedFiles(fileName, + QStringList() + << "mimetype" + << "stack.xml" + << "mergedimage.png"); + } + + return error; +} + #include diff --git a/plugins/impex/ora/ora_export.h b/plugins/impex/ora/ora_export.h index 2e06e207f3..a9e1a657d3 100644 --- a/plugins/impex/ora/ora_export.h +++ b/plugins/impex/ora/ora_export.h @@ -1,36 +1,37 @@ /* * Copyright (c) 2007 Cyrille Berger * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ORA_EXPORT_H_ #define _ORA_EXPORT_H_ #include #include class OraExport : public KisImportExportFilter { Q_OBJECT public: OraExport(QObject *parent, const QVariantList &); ~OraExport() override; public: KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; + QString verify(const QString &fileName) const override; }; #endif diff --git a/plugins/impex/tiff/kis_tiff_converter.cc b/plugins/impex/tiff/kis_tiff_converter.cc index e49dfc21c5..b68057ec87 100644 --- a/plugins/impex/tiff/kis_tiff_converter.cc +++ b/plugins/impex/tiff/kis_tiff_converter.cc @@ -1,770 +1,812 @@ /* * Copyright (c) 2005-2006 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 "kis_tiff_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tiff_reader.h" #include "kis_tiff_ycbcr_reader.h" #include "kis_buffer_stream.h" #include "kis_tiff_writer_visitor.h" #include #if TIFFLIB_VERSION < 20111221 typedef size_t tmsize_t; #endif namespace { QPair getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth) { + const int bits32 = 32; + const int bits16 = 16; + const int bits8 = 8; + if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) { if (nbchannels == 0) nbchannels = 1; extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(GrayAColorModelID.id(), Float32BitsColorDepthID.id()); } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()); } - else { + else /* if (color_nb_bits == bits16) */ { destDepth = 16; return QPair(GrayAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(RGBAColorModelID.id(), Float32BitsColorDepthID.id()); } - return QPair(); + return QPair(); // sanity check; no support for float of higher or lower bit depth } else { if (color_nb_bits <= 8) { destDepth = 8; return QPair(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); - } - else { + } else /* if (color_nb_bits == bits16) */ { destDepth = 16; return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } } } else if (color_type == PHOTOMETRIC_YCBCR) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of + if (sampletype == SAMPLEFORMAT_IEEEFP) { + return QPair(); // sanity check; no support for float + } if (color_nb_bits <= 8) { destDepth = 8; return QPair(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()); } - else { + else if (color_nb_bits == bits16) { destDepth = 16; return QPair(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()); + } else { + return QPair(); // sanity check; no support integers of higher bit depth } } else if (color_type == PHOTOMETRIC_SEPARATED) { if (nbchannels == 0) nbchannels = 4; // SEPARATED is in general CMYK but not always, so we check uint16 inkset; if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) { dbgFile << "Image does not define the inkset."; inkset = 2; } if (inkset != INKSET_CMYK) { dbgFile << "Unsupported inkset (right now, only CMYK is supported)"; char** ink_names; uint16 numberofinks; if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) { dbgFile << "Inks are :"; for (uint i = 0; i < numberofinks; i++) { dbgFile << ink_names[i]; } } else { dbgFile << "inknames are not defined !"; // To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file : if (nbchannels - extrasamplescount != 4) { return QPair(); } + // else - assume it's CMYK and proceed } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()); - } - else { + } else if (color_nb_bits == 16) { destDepth = 16; return QPair(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()); + } else if (sampletype == SAMPLEFORMAT_IEEEFP) { + destDepth = bits32; + return QPair(CMYKAColorModelID.id(), Float32BitsColorDepthID.id()); + } else { + return QPair(); // no support for other bit depths } } else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) { - destDepth = 16; if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count - return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); + + switch(color_nb_bits) { + case bits32: { + destDepth = bits32; + return QPair(LABAColorModelID.id(), Float32BitsColorDepthID.id()); + } + case bits16: { + destDepth = bits16; + if (sampletype == SAMPLEFORMAT_IEEEFP) { + return QPair(LABAColorModelID.id(), Float16BitsColorDepthID.id()); + } + else { + return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); + } + } + case bits8: { + destDepth = bits8; + return QPair(LABAColorModelID.id(), Integer8BitsColorDepthID.id()); + } + default: { + return QPair(); + } + } } else if (color_type == PHOTOMETRIC_PALETTE) { destDepth = 16; if (nbchannels == 0) nbchannels = 2; extrasamplescount = nbchannels - 2; // FIX the extrasamples count // <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } return QPair(); } } KisPropertiesConfigurationSP KisTIFFOptions::toProperties() const { QHash compToIndex; compToIndex[COMPRESSION_NONE] = 0; compToIndex[COMPRESSION_JPEG] = 1; compToIndex[COMPRESSION_DEFLATE] = 2; compToIndex[COMPRESSION_LZW] = 3; compToIndex[COMPRESSION_PIXARLOG] = 8; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("compressiontype", compToIndex.value(compressionType, 0)); cfg->setProperty("predictor", predictor - 1); cfg->setProperty("alpha", alpha); cfg->setProperty("flatten", flatten); cfg->setProperty("quality", jpegQuality); cfg->setProperty("deflate", deflateCompress); cfg->setProperty("pixarlog", pixarLogCompress); cfg->setProperty("saveProfile", saveProfile); return cfg; } void KisTIFFOptions::fromProperties(KisPropertiesConfigurationSP cfg) { QHash indexToComp; indexToComp[0] = COMPRESSION_NONE; indexToComp[1] = COMPRESSION_JPEG; indexToComp[2] = COMPRESSION_DEFLATE; indexToComp[3] = COMPRESSION_LZW; indexToComp[4] = COMPRESSION_PIXARLOG; // old value that might be still stored in a config (remove after Krita 5.0 :) ) indexToComp[8] = COMPRESSION_PIXARLOG; compressionType = indexToComp.value( cfg->getInt("compressiontype", 0), COMPRESSION_NONE); predictor = cfg->getInt("predictor", 0) + 1; alpha = cfg->getBool("alpha", true); flatten = cfg->getBool("flatten", true); jpegQuality = cfg->getInt("quality", 80); deflateCompress = cfg->getInt("deflate", 6); pixarLogCompress = cfg->getInt("pixarlog", 6); saveProfile = cfg->getBool("saveProfile", true); } KisTIFFConverter::KisTIFFConverter(KisDocument *doc) { m_doc = doc; m_stop = false; TIFFSetWarningHandler(0); TIFFSetErrorHandler(0); } KisTIFFConverter::~KisTIFFConverter() { } KisImportExportErrorCode KisTIFFConverter::decode(const QString &filename) { dbgFile << "Start decoding TIFF File"; // Opent the TIFF file TIFF *image = 0; if (!KisImportExportAdditionalChecks::doesFileExist(filename)) { return ImportExportCodes::FileNotExist; } if (!KisImportExportAdditionalChecks::isFileReadable(filename)) { return ImportExportCodes::NoAccessToRead; } if ((image = TIFFOpen(QFile::encodeName(filename), "r")) == 0) { dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << filename; return (ImportExportCodes::FileFormatIncorrect); } do { dbgFile << "Read new sub-image"; KisImportExportErrorCode result = readTIFFDirectory(image); if (!result.isOk()) { return result; } } while (TIFFReadDirectory(image)); // Freeing memory TIFFClose(image); return ImportExportCodes::OK; } KisImportExportErrorCode KisTIFFConverter::readTIFFDirectory(TIFF* image) { // Read information about the tiff uint32 width, height; if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) { dbgFile << "Image does not define its width"; TIFFClose(image); return ImportExportCodes::FileFormatIncorrect; } if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) { dbgFile << "Image does not define its height"; TIFFClose(image); return ImportExportCodes::FileFormatIncorrect; } float xres; if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) { dbgFile << "Image does not define x resolution"; // but we don't stop xres = 100; } float yres; if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) { dbgFile << "Image does not define y resolution"; // but we don't stop yres = 100; } uint16 depth; if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) { dbgFile << "Image does not define its depth"; depth = 1; } uint16 sampletype; if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) { dbgFile << "Image does not define its sample type"; sampletype = SAMPLEFORMAT_UINT; } // Determine the number of channels (useful to know if a file has an alpha or not uint16 nbchannels; if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) { dbgFile << "Image has an undefined number of samples per pixel"; nbchannels = 0; } // Get the number of extrasamples and information about them uint16 *sampleinfo = 0, extrasamplescount; if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) { extrasamplescount = 0; } // Determine the colorspace uint16 color_type; if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) { dbgFile << "Image has an undefined photometric interpretation"; color_type = PHOTOMETRIC_MINISWHITE; } uint8 dstDepth = 0; QPair colorSpaceIdTag = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); if (colorSpaceIdTag.first.isEmpty()) { dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth; TIFFClose(image); return ImportExportCodes::FormatColorSpaceUnsupported; } dbgFile << "Colorspace is :" << colorSpaceIdTag.first << colorSpaceIdTag.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; // Read image profile dbgFile << "Reading profile"; const KoColorProfile* profile = 0; quint32 EmbedLen; quint8* EmbedBuffer; if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) { dbgFile << "Profile found"; QByteArray rawdata; rawdata.resize(EmbedLen); memcpy(rawdata.data(), EmbedBuffer, EmbedLen); profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceIdTag.first, colorSpaceIdTag.second, rawdata); } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceIdTag.first, colorSpaceIdTag.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceIdTag.first << " " << colorSpaceIdTag.second; profile = 0; } // Do not use the linear gamma profile for 16 bits/channel by default, tiff files are usually created with // gamma correction. XXX: Should we ask the user? if (!profile) { + dbgFile << "No profile found; trying to assign a default one."; if (colorSpaceIdTag.first == RGBAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); } else if (colorSpaceIdTag.first == GrayAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); + } else if (colorSpaceIdTag.first == CMYKAColorModelID.id()) { + profile = KoColorSpaceRegistry::instance()->profileByName("Chemical proof"); + } else if (colorSpaceIdTag.first == LABAColorModelID.id()) { + profile = KoColorSpaceRegistry::instance()->profileByName("Lab identity build-in"); + } + if (!profile) { + dbgFile << "No suitable default profile found."; } } // Retrieve a pointer to the colorspace const KoColorSpace* cs = 0; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, 0); } if (cs == 0) { dbgFile << "Colorspace" << colorSpaceIdTag.first << colorSpaceIdTag.second << " is not available, please check your installation."; TIFFClose(image); return ImportExportCodes::FormatColorSpaceUnsupported; } // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { dbgFile << "The profile can't be used in krita, need conversion"; transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Check if there is an alpha channel int8 alphapos = -1; // <- no alpha // Check which extra is alpha if any dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels"; if (sampleinfo) { // index images don't have any sampleinfo, and therefore sampleinfo == 0 for (int i = 0; i < extrasamplescount; i ++) { dbgFile << "sample" << i << "extra sample count" << extrasamplescount << "color channel count" << (cs->colorChannelCount()) << "Number of channels" << nbchannels << "sample info" << sampleinfo[i]; if (sampleinfo[i] == EXTRASAMPLE_UNSPECIFIED) { qWarning() << "Extra sample type not defined for this file, assuming unassociated alpha."; alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) { // XXX: dangelo: the color values are already multiplied with // the alpha value. This needs to be reversed later (postprocessor?) qWarning() << "Associated alpha in this file: krita does not handle plremultiplied alpha."; alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) { // color values are not premultiplied with alpha, and can be used as they are. alphapos = i; } } } dbgFile << "Alpha pos:" << alphapos; // Read META Information KoDocumentInfo * info = m_doc->documentInfo(); char* text; if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) { info->setAuthorInfo("creator", text); } if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) { info->setAboutInfo("title", text); } if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) { info->setAboutInfo("description", text); } // Get the planar configuration uint16 planarconfig; if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) { dbgFile << "Plannar configuration is not define"; TIFFClose(image); return ImportExportCodes::FileFormatIncorrect; } // Creating the KisImageSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points Q_CHECK_PTR(m_image); } else { if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) { quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width(); quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height(); m_image->resizeImage(QRect(0,0,newwidth, newheight)); } } KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX); tdata_t buf = 0; tdata_t* ps_buf = 0; // used only for planar configuration separated KisBufferStreamBase* tiffstream; KisTIFFReaderBase* tiffReader = 0; quint8 poses[5]; KisTIFFPostProcessor* postprocessor = 0; // Configure poses uint8 nbcolorsamples = nbchannels - extrasamplescount; switch (color_type) { case PHOTOMETRIC_MINISWHITE: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples); } break; case PHOTOMETRIC_MINISBLACK: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_CIELAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples); } break; case PHOTOMETRIC_ICCLAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_RGB: { if (sampletype == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_SEPARATED: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; default: break; } // Initisalize tiffReader uint16 * lineSizeCoeffs = new uint16[nbchannels]; uint16 vsubsampling = 1; uint16 hsubsampling = 1; for (uint i = 0; i < nbchannels; i++) { lineSizeCoeffs[i] = 1; } if (color_type == PHOTOMETRIC_PALETTE) { uint16 *red; // No need to free them they are free by libtiff uint16 *green; uint16 *blue; if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) { dbgFile << "Indexed image does not define a palette"; TIFFClose(image); delete [] lineSizeCoeffs; return ImportExportCodes::FileFormatIncorrect; } tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (color_type == PHOTOMETRIC_YCBCR) { TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling); lineSizeCoeffs[1] = hsubsampling; lineSizeCoeffs[2] = hsubsampling; uint16 position; TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position); if (dstDepth == 8) { tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } else if (dstDepth == 16) { tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } } else if (dstDepth == 8) { tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (dstDepth == 16) { uint16 alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue = 15360; // representation of 1.0 in half } else { alphaValue = quint16_MAX; } tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue); } else if (dstDepth == 32) { union { float f; uint32 i; } alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue.f = 1.0f; } else { alphaValue.i = quint32_MAX; } tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i); } if (!tiffReader) { delete postprocessor; delete[] lineSizeCoeffs; TIFFClose(image); dbgFile << "Image has an invalid/unsupported color type: " << color_type; return ImportExportCodes::FileFormatIncorrect; } if (TIFFIsTiled(image)) { dbgFile << "tiled image"; uint32 tileWidth, tileHeight; uint32 x, y; TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight); uint32 linewidth = (tileWidth * depth * nbchannels) / 8; if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(TIFFTileSize(image)); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth); } } else { ps_buf = new tdata_t[nbchannels]; uint32 * lineSizes = new uint32[nbchannels]; tmsize_t baseSize = TIFFTileSize(image) / nbchannels; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(baseSize); lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount(); for (y = 0; y < height; y += tileHeight) { for (x = 0; x < width; x += tileWidth) { dbgFile << "Reading tile x =" << x << " y =" << y; if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadTile(image, ps_buf[i], x, y, 0, i); } } uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x; for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) { tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream); yintile += 1; tiffstream->moveToLine(yintile); } tiffstream->restart(); } } } else { dbgFile << "striped image"; tsize_t stripsize = TIFFStripSize(image); uint32 rowsPerStrip; TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); dbgFile << rowsPerStrip << "" << height; rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(stripsize); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip); } } else { ps_buf = new tdata_t[nbchannels]; uint32 scanLineSize = stripsize / rowsPerStrip; dbgFile << " scanLineSize for each plan =" << scanLineSize; uint32 * lineSizes = new uint32[nbchannels]; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(stripsize); lineSizes[i] = scanLineSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip; uint32 y = 0; dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize; for (uint32 strip = 0; y < height; strip++) { if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1); } } for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) { uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream); y += linesread; yinstrip += linesread; tiffstream->moveToLine(yinstrip); } tiffstream->restart(); } } tiffReader->finalize(); delete[] lineSizeCoeffs; delete tiffReader; delete tiffstream; if (planarconfig == PLANARCONFIG_CONTIG) { _TIFFfree(buf); } else { for (uint i = 0; i < nbchannels; i++) { _TIFFfree(ps_buf[i]); } delete[] ps_buf; } m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data()); return ImportExportCodes::OK; } KisImportExportErrorCode KisTIFFConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP KisTIFFConverter::image() { return m_image; } KisImportExportErrorCode KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) { dbgFile << "Start writing TIFF File"; KIS_ASSERT_RECOVER_RETURN_VALUE(kisimage, ImportExportCodes::InternalError); // Open file for writing TIFF *image; if ((image = TIFFOpen(QFile::encodeName(filename), "w")) == 0) { dbgFile << "Could not open the file for writing" << filename; return ImportExportCodes::NoAccessToWrite; } // Set the document information KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { if (!TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData())) { TIFFClose(image); return ImportExportCodes::ErrorWhileWriting; } } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { if (!TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData())) { TIFFClose(image); return ImportExportCodes::ErrorWhileWriting; } } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { if(!TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData())) { TIFFClose(image); return ImportExportCodes::ErrorWhileWriting; } } dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes()); if (!TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes()))) { // It is the "invert" macro because we convert from pointer-per-inchs to points TIFFClose(image); return ImportExportCodes::ErrorWhileWriting; } if (!TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes()))) { TIFFClose(image); return ImportExportCodes::ErrorWhileWriting; } KisGroupLayer* root = dynamic_cast(kisimage->rootLayer().data()); KIS_ASSERT_RECOVER(root) { TIFFClose(image); return ImportExportCodes::InternalError; } KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options); if (!(visitor->visit(root))) { TIFFClose(image); return ImportExportCodes::Failure; } TIFFClose(image); return ImportExportCodes::OK; } void KisTIFFConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/tiff/kis_tiff_writer_visitor.cpp b/plugins/impex/tiff/kis_tiff_writer_visitor.cpp index a2db8503e0..dccf5530b0 100644 --- a/plugins/impex/tiff/kis_tiff_writer_visitor.cpp +++ b/plugins/impex/tiff/kis_tiff_writer_visitor.cpp @@ -1,224 +1,276 @@ /* * Copyright (c) 2006 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 "kis_tiff_writer_visitor.h" #include #include #include +#include #include #ifdef HAVE_OPENEXR #include #endif namespace { - bool writeColorSpaceInformation(TIFF* image, const KoColorSpace * cs, uint16& color_type, uint16& sample_format) + bool isBitDepthFloat(QString depth) { + return depth.contains("F"); + } + + bool writeColorSpaceInformation(TIFF* image, const KoColorSpace * cs, uint16& color_type, uint16& sample_format, const KoColorSpace* &destColorSpace) { dbgKrita << cs->id(); - if (cs->id() == "GRAYA" || cs->id() == "GRAYAU16") { - color_type = PHOTOMETRIC_MINISBLACK; - return true; - } - if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { + QString id = cs->id(); + QString depth = cs->colorDepthId().id(); + // destColorSpace should be reassigned to a proper color space to convert to + // if the return value of this function is false + destColorSpace = 0; + + // sample_format and color_type should be assigned to the destination color space, + // not /always/ the one we get here + + if (id.contains("RGBA")) { color_type = PHOTOMETRIC_RGB; + if (isBitDepthFloat(depth)) { + sample_format = SAMPLEFORMAT_IEEEFP; + } return true; - } - if (KoID(cs->id()) == KoID("RGBAF16") || KoID(cs->id()) == KoID("RGBAF32")) { - color_type = PHOTOMETRIC_RGB; - sample_format = SAMPLEFORMAT_IEEEFP; - return true; - } - if (cs->id() == "CMYK" || cs->id() == "CMYKAU16") { + + } else if (id.contains("CMYK")) { color_type = PHOTOMETRIC_SEPARATED; TIFFSetField(image, TIFFTAG_INKSET, INKSET_CMYK); + + if (depth == "F16") { + sample_format = SAMPLEFORMAT_IEEEFP; + destColorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), cs->profile()); + return false; + } + + if (isBitDepthFloat(depth)) { + sample_format = SAMPLEFORMAT_IEEEFP; + } return true; - } - if (cs->id() == "LABA") { + + } else if (id.contains("LABA")) { color_type = PHOTOMETRIC_ICCLAB; + + if (depth == "F16") { + sample_format = SAMPLEFORMAT_IEEEFP; + destColorSpace = KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), cs->profile()); + return false; + } + + if (isBitDepthFloat(depth)) { + sample_format = SAMPLEFORMAT_IEEEFP; + } return true; + + } else if (id.contains("GRAYA")) { + color_type = PHOTOMETRIC_MINISBLACK; + if (isBitDepthFloat(depth)) { + sample_format = SAMPLEFORMAT_IEEEFP; + } + return true; + + } else { + color_type = PHOTOMETRIC_RGB; + const QString profile = "sRGB-elle-V2-srgbtrc"; + destColorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), depth, profile); + if (isBitDepthFloat(depth)) { + sample_format = SAMPLEFORMAT_IEEEFP; + } + return false; } - return false; } } KisTIFFWriterVisitor::KisTIFFWriterVisitor(TIFF*image, KisTIFFOptions* options) : m_image(image) , m_options(options) { } KisTIFFWriterVisitor::~KisTIFFWriterVisitor() { } bool KisTIFFWriterVisitor::copyDataToStrips(KisHLineConstIteratorSP it, tdata_t buff, uint8 depth, uint16 sample_format, uint8 nbcolorssamples, quint8* poses) { if (depth == 32) { Q_ASSERT(sample_format == SAMPLEFORMAT_IEEEFP); float *dst = reinterpret_cast(buff); do { const float *d = reinterpret_cast(it->oldRawData()); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; } else if (depth == 16 ) { if (sample_format == SAMPLEFORMAT_IEEEFP) { #ifdef HAVE_OPENEXR half *dst = reinterpret_cast(buff); do { const half *d = reinterpret_cast(it->oldRawData()); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; #endif } else { quint16 *dst = reinterpret_cast(buff); do { const quint16 *d = reinterpret_cast(it->oldRawData()); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; } } else if (depth == 8) { quint8 *dst = reinterpret_cast(buff); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; } return false; } bool KisTIFFWriterVisitor::saveLayerProjection(KisLayer *layer) { dbgFile << "visiting on layer" << layer->name() << ""; KisPaintDeviceSP pd = layer->projection(); + + uint16 color_type; + uint16 sample_format = SAMPLEFORMAT_UINT; + const KoColorSpace* destColorSpace; + // Check colorspace + if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format, destColorSpace)) { // unsupported colorspace + if (!destColorSpace) { + return false; + } + pd.attach(new KisPaintDevice(*pd)); + pd->convertTo(destColorSpace); + } + // Save depth int depth = 8 * pd->pixelSize() / pd->channelCount(); TIFFSetField(image(), TIFFTAG_BITSPERSAMPLE, depth); // Save number of samples if (m_options->alpha) { TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount()); uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA }; TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 1, sampleinfo); } else { TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount() - 1); TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 0); } + // Save colorspace information - uint16 color_type; - uint16 sample_format = SAMPLEFORMAT_UINT; - if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format)) { // unsupported colorspace - return false; - } TIFFSetField(image(), TIFFTAG_PHOTOMETRIC, color_type); TIFFSetField(image(), TIFFTAG_SAMPLEFORMAT, sample_format); TIFFSetField(image(), TIFFTAG_IMAGEWIDTH, layer->image()->width()); TIFFSetField(image(), TIFFTAG_IMAGELENGTH, layer->image()->height()); // Set the compression options TIFFSetField(image(), TIFFTAG_COMPRESSION, m_options->compressionType); TIFFSetField(image(), TIFFTAG_JPEGQUALITY, m_options->jpegQuality); TIFFSetField(image(), TIFFTAG_ZIPQUALITY, m_options->deflateCompress); TIFFSetField(image(), TIFFTAG_PIXARLOGQUALITY, m_options->pixarLogCompress); // Set the predictor TIFFSetField(image(), TIFFTAG_PREDICTOR, m_options->predictor); // Use contiguous configuration TIFFSetField(image(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); // Use 8 rows per strip TIFFSetField(image(), TIFFTAG_ROWSPERSTRIP, 8); // Save profile if (m_options->saveProfile) { const KoColorProfile* profile = pd->colorSpace()->profile(); if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) { QByteArray ba = profile->rawData(); TIFFSetField(image(), TIFFTAG_ICCPROFILE, ba.size(), ba.constData()); } } tsize_t stripsize = TIFFStripSize(image()); tdata_t buff = _TIFFmalloc(stripsize); qint32 height = layer->image()->height(); qint32 width = layer->image()->width(); bool r = true; for (int y = 0; y < height; y++) { KisHLineConstIteratorSP it = pd->createHLineConstIteratorNG(0, y, width); switch (color_type) { case PHOTOMETRIC_MINISBLACK: { quint8 poses[] = { 0, 1 }; r = copyDataToStrips(it, buff, depth, sample_format, 1, poses); } break; case PHOTOMETRIC_RGB: { quint8 poses[4]; if (sample_format == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } r = copyDataToStrips(it, buff, depth, sample_format, 3, poses); } break; case PHOTOMETRIC_SEPARATED: { quint8 poses[] = { 0, 1, 2, 3, 4 }; r = copyDataToStrips(it, buff, depth, sample_format, 4, poses); } break; case PHOTOMETRIC_ICCLAB: { quint8 poses[] = { 0, 1, 2, 3 }; r = copyDataToStrips(it, buff, depth, sample_format, 3, poses); } break; return false; } if (!r) return false; TIFFWriteScanline(image(), buff, y, (tsample_t) - 1); } _TIFFfree(buff); TIFFWriteDirectory(image()); return true; } diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index 111eafcfd2..fd5d079426 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,141 +1,201 @@ /* * Copyright (C) 2007 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 "kis_tiff_test.h" #include #include #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.h" #include +#include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif const QString TiffMimetype = "image/tiff"; void KisTiffTest::testFiles() { // XXX: make the exiv io backends real plugins KisExiv2::initialize(); QStringList excludes; #ifndef CPU_32_BITS excludes << "flower-minisblack-06.tif"; #endif #ifdef HAVE_LCMS2 excludes << "flower-separated-contig-08.tif" << "flower-separated-contig-16.tif" << "flower-separated-planar-08.tif" << "flower-separated-planar-16.tif" << "flower-minisblack-02.tif" << "flower-minisblack-04.tif" << "flower-minisblack-08.tif" << "flower-minisblack-10.tif" << "flower-minisblack-12.tif" << "flower-minisblack-14.tif" << "flower-minisblack-16.tif" << "flower-minisblack-24.tif" << "flower-minisblack-32.tif" << "jim___dg.tif" << "jim___gg.tif" << "strike.tif"; #endif excludes << "text.tif" << "ycbcr-cat.tif"; TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes, QString(), 1); } void KisTiffTest::testRoundTripRGBF16() { // Disabled for now, it's broken because we assumed integers. #if 0 QRect testRect(0,0,1000,1000); QRect fillRect(100,100,100,100); const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0); KisDocument *doc0 = qobject_cast(KisPart::instance()->createDocument()); doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0); QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff")); tmpFile.open(); doc0->setBackupFile(false); doc0->setOutputMimeType("image/tiff"); doc0->setFileBatchMode(true); doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName())); KisNodeSP layer0 = doc0->image()->root()->firstChild(); Q_ASSERT(layer0); layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16)); KisDocument *doc1 = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc1); doc1->setFileBatchMode(false); KisImportExportErrorCode status; QString s = manager.importDocument(tmpFile.fileName(), QString(), status); dbgKrita << s; Q_ASSERT(doc1->image()); QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect); QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect); QCOMPARE(ref0, ref1); #endif } +void KisTiffTest::testSaveTiffColorSpace(QString colorModel, QString colorDepth, QString colorProfile) +{ + KoColorSpaceRegistry registry; + const KoColorSpace *space = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); + TestUtil::testExportToColorSpace(QString(FILES_DATA_DIR), TiffMimetype, space, ImportExportCodes::OK, true); +} + +void KisTiffTest::testSaveTiffRgbaColorSpace() +{ + QString profile = "sRGB-elle-V2-srgbtrc"; + testSaveTiffColorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + profile = "sRGB-elle-V2-g10"; + testSaveTiffColorSpace(RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); +} + +void KisTiffTest::testSaveTiffGreyAColorSpace() +{ + QString profile = "Gray-D50-elle-V2-srgbtrc"; + testSaveTiffColorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + profile = "Gray-D50-elle-V2-g10"; + testSaveTiffColorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(GrayAColorModelID.id(), Float16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), profile); + +} + + +void KisTiffTest::testSaveTiffCmykColorSpace() +{ + QString profile = "Chemical proof"; + testSaveTiffColorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); +} + +void KisTiffTest::testSaveTiffLabColorSpace() +{ + const QString profile = "Lab identity build-in"; + testSaveTiffColorSpace(LABAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), profile); +} + + +void KisTiffTest::testSaveTiffYCrCbAColorSpace() +{ + /* + * There is no public/open profile for YCrCbA, so no way to test it... + * + const QString profile = ""; + testSaveTiffColorSpace(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); + testSaveTiffColorSpace(YCbCrAColorModelID.id(), Float32BitsColorDepthID.id(), profile); + */ +} + + void KisTiffTest::testImportFromWriteonly() { TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TiffMimetype); } void KisTiffTest::testExportToReadonly() { TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TiffMimetype, true); } void KisTiffTest::testImportIncorrectFormat() { TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TiffMimetype); } KISTEST_MAIN(KisTiffTest) diff --git a/plugins/impex/tiff/tests/kis_tiff_test.h b/plugins/impex/tiff/tests/kis_tiff_test.h index 11505376f7..873a1caf89 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.h +++ b/plugins/impex/tiff/tests/kis_tiff_test.h @@ -1,36 +1,43 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_TIFF_TEST_H_ #define _KIS_TIFF_TEST_H_ #include class KisTiffTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); void testRoundTripRGBF16(); + void testSaveTiffColorSpace(QString colorModel, QString colorDepth, QString colorProfile); + void testSaveTiffRgbaColorSpace(); + void testSaveTiffGreyAColorSpace(); + void testSaveTiffCmykColorSpace(); + void testSaveTiffLabColorSpace(); + void testSaveTiffYCrCbAColorSpace(); + void testImportFromWriteonly(); void testExportToReadonly(); void testImportIncorrectFormat(); }; #endif diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index 1382e84c86..dc9662b966 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,419 +1,421 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * 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_brush_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_brush_server.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_multipliers_double_slider_spinbox.h" #include "kis_spacing_selection_widget.h" #include "kis_signals_blocker.h" #include "kis_imagepipe_brush.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include #include "kis_global.h" #include "kis_gbr_brush.h" #include "kis_debug.h" #include "kis_image.h" /// The resource item delegate for rendering the resource preview class KisBrushDelegate : public QAbstractItemDelegate { public: KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisBrushDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; KisBrush *brush = static_cast(index.internalPointer()); QRect itemRect = option.rect; QImage thumbnail = brush->image(); if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) { thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->save(); int dx = (itemRect.width() - thumbnail.width()) / 2; int dy = (itemRect.height() - thumbnail.height()) / 2; painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->drawRect(option.rect); painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(0.65); painter->fillRect(option.rect, option.palette.highlight()); } painter->restore(); } KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name) : QWidget(parent), m_stampBrushWidget(0), m_clipboardBrushWidget(0) { setObjectName(name); setupUi(this); brushSizeSpinBox->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); brushSizeSpinBox->setValue(5); brushSizeSpinBox->setExponentRatio(3.0); brushSizeSpinBox->setSuffix(i18n(" px")); brushSizeSpinBox->setExponentRatio(3.0); QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); brushRotationSpinBox->setRange(0, 360, 0); brushRotationSpinBox->setValue(0); brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree)); QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); brushSpacingSelectionWidget->setSpacing(true, 1.0); connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); QObject::connect(useColorAsMaskCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetItemUseColorAsMask(bool))); KisBrushResourceServer* rServer = KisBrushServer::instance()->brushServer(); QSharedPointer adapter(new KisBrushResourceServerAdapter(rServer)); m_itemChooser = new KoResourceItemChooser(adapter, this); m_itemChooser->setObjectName("brush_selector"); m_itemChooser->showTaggingBar(true); m_itemChooser->setColumnCount(10); m_itemChooser->setRowHeight(30); m_itemChooser->setItemDelegate(new KisBrushDelegate(this)); m_itemChooser->setCurrentItem(0, 0); m_itemChooser->setSynced(true); m_itemChooser->setMinimumWidth(100); m_itemChooser->setMinimumHeight(150); m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them addPresetButton->setIcon(KisIconUtils::loadIcon("list-add")); deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty")); connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource())); connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource())); presetsLayout->addWidget(m_itemChooser); connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(updateBrushTip(KoResource*))); stampButton->setIcon(KisIconUtils::loadIcon("list-add")); stampButton->setToolTip(i18n("Creates a brush tip from the current image selection." "\n If no selection is present the whole image will be used.")); clipboardButton->setIcon(KisIconUtils::loadIcon("list-add")); clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard.")); connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush())); connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush())); QGridLayout *spacingLayout = new QGridLayout(); spacingLayout->setObjectName("spacing grid layout"); resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0")); connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush())); updateBrushTip(m_itemChooser->currentResource()); } KisPredefinedBrushChooser::~KisPredefinedBrushChooser() { } void KisPredefinedBrushChooser::setBrush(KisBrushSP brush) { /** * Warning: since the brushes are always cloned after loading from XML or * fetching from the server, we cannot just ask for that brush explicitly. * Instead, we should search for the brush with the same filename and/or name * and load it. Please take it into account that after selecting the brush * explicitly in the chooser, m_itemChooser->currentResource() might be * **not** the same as the value in m_brush. * * Ideally, if the resource is not found on the server, we should add it, but * it might lead to a set of weird consequences. So for now we just * select nothing. */ KisBrushResourceServer* server = KisBrushServer::instance()->brushServer(); KoResource *resource = server->resourceByFilename(brush->shortFilename()).data(); if (!resource) { resource = server->resourceByName(brush->name()).data(); } if (!resource) { resource = brush.data(); } m_itemChooser->setCurrentResource(resource); updateBrushTip(brush.data(), true); } void KisPredefinedBrushChooser::slotResetBrush() { /** * The slot also resets the brush on the server * * TODO: technically, after we refactored all the brushes to be forked, * we can just re-update the brush from the server without reloading. * But it needs testing. */ KisBrush *brush = dynamic_cast(m_itemChooser->currentResource()); if (brush) { brush->load(); brush->setScale(1.0); brush->setAngle(0.0); updateBrushTip(brush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { int brushWidth = m_brush->width(); m_brush->setScale(sizeValue / qreal(brushWidth)); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setAngle(rotationValue / 180.0 * M_PI); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSpacingChanged() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemUseColorAsMask(bool useColorAsMask) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); KisGbrBrush *brush = dynamic_cast(m_brush.data()); if (brush) { brush->setUseColorAsMask(useColorAsMask); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotOpenStampBrush() { if(!m_stampBrushWidget) { m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image); m_stampBrushWidget->setModal(false); connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResource*)), SLOT(slotNewPredefinedBrush(KoResource*))); } else { m_stampBrushWidget->setImage(m_image); } QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::slotOpenClipboardBrush() { if(!m_clipboardBrushWidget) { m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image); m_clipboardBrushWidget->setModal(true); connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResource*)), SLOT(slotNewPredefinedBrush(KoResource*))); } QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::updateBrushTip(KoResource * resource, bool isChangingBrushPresets) { QString animatedBrushTipSelectionMode; // incremental, random, etc { KisBrush* brush = dynamic_cast(resource); m_brush = brush ? brush->clone() : 0; } if (m_brush) { brushTipNameLabel->setText(i18n(m_brush->name().toUtf8().data())); QString brushTypeString = ""; if (m_brush->brushType() == INVALID) { brushTypeString = i18n("Invalid"); } else if (m_brush->brushType() == MASK) { brushTypeString = i18n("Mask"); } else if (m_brush->brushType() == IMAGE) { brushTypeString = i18n("GBR"); } else if (m_brush->brushType() == PIPE_MASK ) { brushTypeString = i18n("Animated Mask"); // GIH brush // cast to GIH brush and grab parasite name //m_brush KisImagePipeBrush* pipeBrush = dynamic_cast(resource); animatedBrushTipSelectionMode = pipeBrush->parasiteSelection(); } else if (m_brush->brushType() == PIPE_IMAGE ) { brushTypeString = i18n("Animated Image"); } QString brushDetailsText = QString("%1 (%2 x %3) %4") .arg(brushTypeString) .arg(m_brush->width()) .arg(m_brush->height()) .arg(animatedBrushTipSelectionMode); brushDetailsLabel->setText(brushDetailsText); // keep the current preset's tip settings if we are preserving it // this will set the brush's model data to keep what it currently has for size, spacing, etc. if (preserveBrushPresetSettings->isChecked() && !isChangingBrushPresets) { m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); m_brush->setAngle(brushRotationSpinBox->value() * M_PI / 180); m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setUserEffectiveSize(brushSizeSpinBox->value()); } brushSpacingSelectionWidget->setSpacing(m_brush->autoSpacingActive(), m_brush->autoSpacingActive() ? m_brush->autoSpacingCoeff() : m_brush->spacing()); brushRotationSpinBox->setValue(m_brush->angle() * 180 / M_PI); brushSizeSpinBox->setValue(m_brush->width() * m_brush->scale()); // useColorAsMask support is only in gimp brush so far + bool prevColorAsMaskState = useColorAsMaskCheckbox->isChecked(); KisGbrBrush *gimpBrush = dynamic_cast(m_brush.data()); if (gimpBrush) { - useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask()); + useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask() || prevColorAsMaskState); + gimpBrush->setUseColorAsMask(prevColorAsMaskState); } useColorAsMaskCheckbox->setEnabled(m_brush->hasColor() && gimpBrush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResource *resource) { m_itemChooser->setCurrentResource(resource); updateBrushTip(resource); } void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) { Q_UNUSED(yPixels); qreal oldWidth = m_brush->width() * m_brush->scale(); qreal newWidth = oldWidth + xPixels; newWidth = qMax(newWidth, qreal(0.1)); brushSizeSpinBox->setValue(newWidth); } void KisPredefinedBrushChooser::setImage(KisImageWSP image) { m_image = image; } void KisPredefinedBrushChooser::slotImportNewBrushResource() { m_itemChooser->slotButtonClicked(KoResourceItemChooser::Button_Import); } void KisPredefinedBrushChooser::slotDeleteBrushResource() { m_itemChooser->slotButtonClicked(KoResourceItemChooser::Button_Remove); } #include "moc_kis_brush_chooser.cpp" diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index 3b95393eda..afaae866e1 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1729 +1,1735 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 C. Boemann 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 "DefaultTool.h" #include "DefaultToolGeometryWidget.h" #include "DefaultToolTabbedWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_registry.h" #include "kis_node.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" #include #define HANDLE_DISTANCE 10 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE) #define INNER_HANDLE_DISTANCE_SQ 16 namespace { static const QString EditFillGradientFactoryId = "edit_fill_gradient"; static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient"; enum TransformActionType { TransformRotate90CW, TransformRotate90CCW, TransformRotate180, TransformMirrorX, TransformMirrorY, TransformReset }; enum BooleanOp { BooleanUnion, BooleanIntersection, BooleanSubtraction }; } class NopInteractionStrategy : public KoInteractionStrategy { public: explicit NopInteractionStrategy(KoToolBase *parent) : KoInteractionStrategy(parent) { } KUndo2Command *createCommand() override { return 0; } void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} void paint(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); } }; class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy { public: explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid) : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid) { } void paint(QPainter &painter, const KoViewConverter &converter) override { KoShapeRubberSelectStrategy::paint(painter, converter); } void finishInteraction(Qt::KeyboardModifiers modifiers = 0) override { Q_UNUSED(modifiers); DefaultTool *defaultTool = dynamic_cast(tool()); KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool); KoSelection * selection = defaultTool->koSelection(); const bool useContainedMode = currentMode() == CoveringSelection; QList shapes = defaultTool->shapeManager()-> shapesAt(selectedRectangle(), true, useContainedMode); Q_FOREACH (KoShape * shape, shapes) { if (!shape->isSelectable()) continue; selection->select(shape); } defaultTool->repaintDecorations(); defaultTool->canvas()->updateCanvas(selectedRectangle()); } }; #include #include "KoShapeGradientHandles.h" #include "ShapeGradientEditStrategy.h" class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory { public: MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q) : KoInteractionStrategyFactory(priority, id), q(_q), m_fillVariant(fillVariant) { } KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { KoShape *shape = onlyEditableShape(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point); } return 0; } bool hoverEvent(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); return false; } bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); return false; } bool tryUseCustomCursor() override { if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { q->useCursor(Qt::OpenHandCursor); } return m_currentHandle.type != KoShapeGradientHandles::Handle::None; } private: KoShape* onlyEditableShape() const { KoSelection *selection = q->koSelection(); QList shapes = selection->selectedEditableShapes(); KoShape *shape = 0; if (shapes.size() == 1) { shape = shapes.first(); } return shape; } KoShapeGradientHandles::Handle handleAt(const QPointF &pos) { KoShapeGradientHandles::Handle result; KoShape *shape = onlyEditableShape(); if (shape) { KoFlake::SelectionHandle globalHandle = q->handleAt(pos); const qreal distanceThresholdSq = globalHandle == KoFlake::NoHandle ? HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; const KoViewConverter *converter = q->canvas()->viewConverter(); const QPointF viewPoint = converter->documentToView(pos); qreal minDistanceSq = std::numeric_limits::max(); KoShapeGradientHandles sh(m_fillVariant, shape); Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) { const QPointF handlePoint = converter->documentToView(handle.pos); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { result = handle; minDistanceSq = distanceSq; } } } return result; } private: DefaultTool *q; KoFlake::FillVariant m_fillVariant; KoShapeGradientHandles::Handle m_currentHandle; }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { } bool hasSelection() override { if (m_selection) { return m_selection->count(); } return false; } private: QPointer m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeft) , m_mouseWasInsideHandles(false) , m_decorator(0) , m_selectionHandler(new SelectionHandler(this)) , m_tabbedOptionWidget(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; if (connectToSelectedShapesProxy) { connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions())); } } DefaultTool::~DefaultTool() { } void DefaultTool::slotActivateEditFillGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::Fill, 1, EditFillGradientFactoryId, this)); } else { removeInteractionFactory(EditFillGradientFactoryId); } repaintDecorations(); } void DefaultTool::slotActivateEditStrokeGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill, 0, EditStrokeGradientFactoryId, this)); } else { removeInteractionFactory(EditStrokeGradientFactoryId); } repaintDecorations(); } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::addMappedAction(QSignalMapper *mapper, const QString &actionId, int commandType) { QAction *a =action(actionId); connect(a, SIGNAL(triggered()), mapper, SLOT(map())); mapper->setMapping(a, commandType); } void DefaultTool::setupActions() { m_alignSignalsMapper = new QSignalMapper(this); addMappedAction(m_alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment); addMappedAction(m_alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment); addMappedAction(m_alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment); addMappedAction(m_alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment); addMappedAction(m_alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment); addMappedAction(m_alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment); m_distributeSignalsMapper = new QSignalMapper(this); addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution); addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution); m_transformSignalsMapper = new QSignalMapper(this); addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW); addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW); addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180); addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX); addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY); addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset); m_booleanSignalsMapper = new QSignalMapper(this); addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion); addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection); addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction); m_contextMenu.reset(new QMenu()); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { if (tryUseCustomCursor()) return; QCursor cursor = Qt::ArrowCursor; QString statusText; KoSelection *selection = koSelection(); if (selection && selection->count() > 0) { // has a selection bool editable = !selection->selectedEditableShapes().isEmpty(); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { KoSelection *selection = koSelection(); if (selection) { this->m_decorator = new SelectionDecorator(canvas()->resourceManager()); { /** * Selection masks don't render the outline of the shapes, so we should * do that explicitly when rendering them via selection */ KisCanvas2 *kisCanvas = static_cast(canvas()); KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode(); const bool isSelectionMask = node && node->inherits("KisSelectionMask"); m_decorator->setForceShapeOutlines(isSelectionMask); } m_decorator->setSelection(selection); m_decorator->setHandleRadius(handleRadius()); m_decorator->setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); m_decorator->setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); m_decorator->paint(painter, converter); } KoInteractionTool::paint(painter, converter); painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } bool DefaultTool::isValidForCurrentLayer() const { // if the currently active node has a shape manager, then it is // probably our client :) KisCanvas2 *kisCanvas = static_cast(canvas()); return bool(kisCanvas->localShapeManager()); } KoShapeManager *DefaultTool::shapeManager() const { return canvas()->shapeManager(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { // this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it if (!isValidForCurrentLayer()) { KisCanvas2 *kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage( i18n("This tool only works on vector layers. You probably want the move tool."), QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); return; } KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { KoSelection *selection = koSelection(); if (!selection || !selection->count()) return QRectF(); recalcSelectionBox(selection); QRectF bound = m_selectionOutline.boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); // This makes sure the decorations that are shown are refreshed. especally the "T" icon canvas()->updateCanvas(QRectF(0,0,canvas()->canvasWidget()->width(), canvas()->canvasWidget()->height())); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoSelection *selection = koSelection(); KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && selection && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } explicitUserStrokeEndRequest(); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { bool result = false; qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList shapes = koSelection()->selectedEditableShapes(); if (!shapes.isEmpty()) { canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y))); result = true; } } return result; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::repaintDecorations() { if (koSelection() && koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { // all the selected shapes, not only editable! QList shapes = koSelection()->selectedShapes(); if (!shapes.isEmpty()) { KoDrag drag; drag.setSvg(shapes); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, koSelection()->selectedShapes()) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } KoSelection *DefaultTool::koSelection() const { Q_ASSERT(canvas()); Q_ASSERT(canvas()->selectedShapesProxy()); return canvas()->selectedShapesProxy()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; const KoViewConverter *converter = canvas()->viewConverter(); KoSelection *selection = koSelection(); if (!selection || !selection->count() || !converter) { return KoFlake::NoHandle; } recalcSelectionBox(selection); if (innerHandleMeaning) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } const QPointF viewPoint = converter->documentToView(point); for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); // if just inside the outline if (distanceSq < HANDLE_DISTANCE_SQ) { if (innerHandleMeaning) { if (distanceSq < INNER_HANDLE_DISTANCE_SQ) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox(KoSelection *selection) { KIS_ASSERT_RECOVER_RETURN(selection->count()); QTransform matrix = selection->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect())); m_angle = 0.0; QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (selection->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); QAction *actionBringToFront = action("object_order_front"); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection); QAction *actionRaise = action("object_order_raise"); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection); QAction *actionLower = action("object_order_lower"); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = action("object_order_back"); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection); QAction *actionGroupBottom = action("object_group"); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection); QAction *actionUngroupBottom = action("object_ungroup"); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection); QAction *actionSplit = action("object_split"); connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection); connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int))); connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int))); connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int))); connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int))); m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->activate(); } } void DefaultTool::deactivate() { KoToolBase::deactivate(); QAction *actionBringToFront = action("object_order_front"); disconnect(actionBringToFront, 0, this, 0); QAction *actionRaise = action("object_order_raise"); disconnect(actionRaise, 0, this, 0); QAction *actionLower = action("object_order_lower"); disconnect(actionLower, 0, this, 0); QAction *actionSendToBack = action("object_order_back"); disconnect(actionSendToBack, 0, this, 0); QAction *actionGroupBottom = action("object_group"); disconnect(actionGroupBottom, 0, this, 0); QAction *actionUngroupBottom = action("object_ungroup"); disconnect(actionUngroupBottom, 0, this, 0); QAction *actionSplit = action("object_split"); disconnect(actionSplit, 0, this, 0); disconnect(m_alignSignalsMapper, 0, this, 0); disconnect(m_distributeSignalsMapper, 0, this, 0); disconnect(m_transformSignalsMapper, 0, this, 0); disconnect(m_booleanSignalsMapper, 0, this, 0); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->deactivate(); } } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); if (selectedShapes.isEmpty()) return; const int groupZIndex = selectedShapes.last()->zIndex(); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(groupZIndex); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd); canvas()->shapeController()->addShapeDirect(group, 0, cmd); new KoShapeGroupCommand(group, selectedShapes, true, cmd); new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KUndo2Command *cmd = 0; QList newShapes; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, selectedShapes) { KoShapeGroup *group = dynamic_cast(shape); if (group) { if (!cmd) { cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd); } newShapes << group->shapes(); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); } } void DefaultTool::selectionTransform(int transformAction) { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } QTransform applyTransform; bool shouldReset = false; KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action"); switch (TransformActionType(transformAction)) { case TransformRotate90CW: applyTransform.rotate(90.0); actionName = kundo2_i18n("Rotate Object 90° CW"); break; case TransformRotate90CCW: applyTransform.rotate(-90.0); actionName = kundo2_i18n("Rotate Object 90° CCW"); break; case TransformRotate180: applyTransform.rotate(180.0); actionName = kundo2_i18n("Rotate Object 180°"); break; case TransformMirrorX: applyTransform.scale(-1.0, 1.0); actionName = kundo2_i18n("Mirror Object Horizontally"); break; case TransformMirrorY: applyTransform.scale(1.0, -1.0); actionName = kundo2_i18n("Mirror Object Vertically"); break; case TransformReset: shouldReset = true; actionName = kundo2_i18n("Reset Object Transformations"); break; } if (!shouldReset && applyTransform.isIdentity()) return; QList oldTransforms; QList newTransforms; const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes); const QPointF centerPoint = outlineRect.center(); const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y()); const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y()); // we also add selection to the list of transformed shapes, so that its outline is updated correctly QList transformedShapes = editableShapes; transformedShapes << selection; Q_FOREACH (KoShape *shape, transformedShapes) { oldTransforms.append(shape->transformation()); QTransform t; if (!shouldReset) { const QTransform world = shape->absoluteTransformation(0); t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation(); } else { const QPointF center = shape->outlineRect().center(); const QPointF offset = shape->transformation().map(center) - center; t = QTransform::fromTranslate(offset.x(), offset.y()); } newTransforms.append(t); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms); cmd->setText(actionName); canvas()->addCommand(cmd); } void DefaultTool::selectionBooleanOp(int booleanOp) { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } QVector srcOutlines; QPainterPath dstOutline; KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name"); // TODO: implement a reference shape selection dialog! const int referenceShapeIndex = 0; KoShape *referenceShape = editableShapes[referenceShapeIndex]; Q_FOREACH (KoShape *shape, editableShapes) { srcOutlines << shape->absoluteTransformation(0).map(shape->outline()); } if (booleanOp == BooleanUnion) { Q_FOREACH (const QPainterPath &path, srcOutlines) { dstOutline |= path; } actionName = kundo2_i18n("Unite Shapes"); } else if (booleanOp == BooleanIntersection) { for (int i = 0; i < srcOutlines.size(); i++) { if (i == 0) { dstOutline = srcOutlines[i]; } else { dstOutline &= srcOutlines[i]; } } // there is a bug in Qt, sometimes it leaves the resulting // outline open, so just close it explicitly. dstOutline.closeSubpath(); actionName = kundo2_i18n("Intersect Shapes"); } else if (booleanOp == BooleanSubtraction) { for (int i = 0; i < srcOutlines.size(); i++) { dstOutline = srcOutlines[referenceShapeIndex]; if (i != referenceShapeIndex) { dstOutline -= srcOutlines[i]; } } actionName = kundo2_i18n("Subtract Shapes"); } KoShape *newShape = 0; if (!dstOutline.isEmpty()) { newShape = KoPathShape::createShapeFromPainterPath(dstOutline); } KUndo2Command *cmd = new KUndo2Command(actionName); new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd); QList newSelectedShapes; if (newShape) { newShape->setBackground(referenceShape->background()); newShape->setStroke(referenceShape->stroke()); newShape->setZIndex(referenceShape->zIndex()); KoShapeContainer *parent = referenceShape->parent(); canvas()->shapeController()->addShapeDirect(newShape, parent, cmd); newSelectedShapes << newShape; } canvas()->shapeController()->removeShapes(editableShapes, cmd); new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); } void DefaultTool::selectionSplitShapes() { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes")); new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd); QList newShapes; Q_FOREACH (KoShape *shape, editableShapes) { KoPathShape *pathShape = dynamic_cast(shape); if (!pathShape) return; QList splitShapes; if (pathShape->separate(splitShapes)) { QList normalShapes = implicitCastList(splitShapes); KoShapeContainer *parent = shape->parent(); canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd); canvas()->shapeController()->removeShape(shape, cmd); newShapes << normalShapes; } } new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd); canvas()->addCommand(cmd); } void DefaultTool::selectionAlign(int _align) { KoShapeAlignCommand::Align align = static_cast(_align); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceProvider::PageSize)); } else { bb = KoShape::absoluteOutlineRect(editableShapes); } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionDistribute(int _distribute) { KoShapeDistributeCommand::Distribute distribute = static_cast(_distribute); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.size() < 3) { return; } QRectF bb = KoShape::absoluteOutlineRect(editableShapes); KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = koSelection(); if (!selection) { return; } QList selectedShapes = selection->selectedEditableShapes(); if (selectedShapes.isEmpty()) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; m_tabbedOptionWidget = new DefaultToolTabbedWidget(this); if (isActivated()) { m_tabbedOptionWidget->activate(); } widgets.append(m_tabbedOptionWidget); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditFillGradient(bool)), SLOT(slotActivateEditFillGradient(bool))); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditStrokeGradient(bool)), SLOT(slotActivateEditStrokeGradient(bool))); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = KoFlake::AnchorPosition(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { KoSelection *selection = koSelection(); if (!selection) return nullptr; bool insideSelection = false; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = !selection->selectedEditableShapes().isEmpty(); const bool selectMultiple = event->modifiers() & Qt::ShiftModifier; const bool selectNextInStack = event->modifiers() & Qt::ControlModifier; const bool avoidSelection = event->modifiers() & Qt::AltModifier; if (selectNextInStack) { // change the hot selection position when middle clicking on a handle KoFlake::AnchorPosition newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopMiddleHandle: newHotPosition = KoFlake::Top; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRight; break; case KoFlake::RightMiddleHandle: newHotPosition = KoFlake::Right; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRight; break; case KoFlake::BottomMiddleHandle: newHotPosition = KoFlake::Bottom; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeft; break; case KoFlake::LeftMiddleHandle: newHotPosition = KoFlake::Left; break; case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeft; break; case KoFlake::NoHandle: default: // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point); // TODO: use calculated values instead! QPointF centerPt = converter->documentToView(selection->absolutePosition()); if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) { newHotPosition = KoFlake::Center; } break; } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); return new NopInteractionStrategy(this); } } if (!avoidSelection && editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { // resizing or shearing only with left mouse button if (insideSelection) { bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling(); return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, selection, event->point, handle); } // rotating is allowed for right mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, selection, event->point, event->buttons()); } } if (!selectMultiple && !selectNextInStack) { if (insideSelection) { return new ShapeMoveStrategy(this, selection, event->point); } } } KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) { if (!selectMultiple) { repaintDecorations(); selection->deselectAll(); } return new SelectionInteractionStrategy(this, event->point, false); } if (selection->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); selection->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { selection->deselectAll(); } selection->select(shape); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, selection, event->point); } return 0; } void DefaultTool::updateActions() { QList editableShapes; if (koSelection()) { editableShapes = koSelection()->selectedEditableShapes(); } const bool hasEditableShapes = !editableShapes.isEmpty(); action("object_order_front")->setEnabled(hasEditableShapes); action("object_order_raise")->setEnabled(hasEditableShapes); action("object_order_lower")->setEnabled(hasEditableShapes); action("object_order_back")->setEnabled(hasEditableShapes); action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes); action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes); action("object_transform_rotate_180")->setEnabled(hasEditableShapes); action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes); action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes); action("object_transform_reset")->setEnabled(hasEditableShapes); const bool multipleSelected = editableShapes.size() > 1; const bool alignmentEnabled = multipleSelected || (!editableShapes.isEmpty() && canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize)); action("object_align_horizontal_left")->setEnabled(alignmentEnabled); action("object_align_horizontal_center")->setEnabled(alignmentEnabled); action("object_align_horizontal_right")->setEnabled(alignmentEnabled); action("object_align_vertical_top")->setEnabled(alignmentEnabled); action("object_align_vertical_center")->setEnabled(alignmentEnabled); action("object_align_vertical_bottom")->setEnabled(alignmentEnabled); const bool distributionEnabled = editableShapes.size() > 2; action("object_distribute_horizontal_left")->setEnabled(distributionEnabled); action("object_distribute_horizontal_center")->setEnabled(distributionEnabled); action("object_distribute_horizontal_right")->setEnabled(distributionEnabled); action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled); action("object_distribute_vertical_top")->setEnabled(distributionEnabled); action("object_distribute_vertical_center")->setEnabled(distributionEnabled); action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled); action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled); updateDistinctiveActions(editableShapes); emit selectionChanged(editableShapes.size()); } void DefaultTool::updateDistinctiveActions(const QList &editableShapes) { const bool multipleSelected = editableShapes.size() > 1; action("object_group")->setEnabled(multipleSelected); action("object_unite")->setEnabled(multipleSelected); action("object_intersect")->setEnabled(multipleSelected); action("object_subtract")->setEnabled(multipleSelected); bool hasShapesWithMultipleSegments = false; Q_FOREACH (KoShape *shape, editableShapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape && pathShape->subpathCount() > 1) { hasShapesWithMultipleSegments = true; break; } } action("object_split")->setEnabled(hasShapesWithMultipleSegments); bool hasGroupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { hasGroupShape = true; break; } } action("object_ungroup")->setEnabled(hasGroupShape); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QMenu* DefaultTool::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); - m_contextMenu->addAction(action("edit_cut")); - m_contextMenu->addAction(action("edit_copy")); - m_contextMenu->addAction(action("edit_paste")); - - m_contextMenu->addSeparator(); - - m_contextMenu->addAction(action("object_order_front")); - m_contextMenu->addAction(action("object_order_raise")); - m_contextMenu->addAction(action("object_order_lower")); - m_contextMenu->addAction(action("object_order_back")); - - if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { - m_contextMenu->addSeparator(); - m_contextMenu->addAction(action("object_group")); - m_contextMenu->addAction(action("object_ungroup")); - } - + m_contextMenu->addSection(i18n("Vector Shape Actions")); m_contextMenu->addSeparator(); QMenu *transform = m_contextMenu->addMenu(i18n("Transform")); + transform->addAction(action("object_transform_rotate_90_cw")); transform->addAction(action("object_transform_rotate_90_ccw")); transform->addAction(action("object_transform_rotate_180")); transform->addSeparator(); transform->addAction(action("object_transform_mirror_horizontally")); transform->addAction(action("object_transform_mirror_vertically")); transform->addSeparator(); transform->addAction(action("object_transform_reset")); if (action("object_unite")->isEnabled() || action("object_intersect")->isEnabled() || action("object_subtract")->isEnabled() || action("object_split")->isEnabled()) { QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations")); transform->addAction(action("object_unite")); transform->addAction(action("object_intersect")); transform->addAction(action("object_subtract")); transform->addAction(action("object_split")); } + + m_contextMenu->addSeparator(); + + m_contextMenu->addAction(action("edit_cut")); + m_contextMenu->addAction(action("edit_copy")); + m_contextMenu->addAction(action("edit_paste")); + + m_contextMenu->addSeparator(); + + m_contextMenu->addAction(action("object_order_front")); + m_contextMenu->addAction(action("object_order_raise")); + m_contextMenu->addAction(action("object_order_lower")); + m_contextMenu->addAction(action("object_order_back")); + + if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { + m_contextMenu->addSeparator(); + m_contextMenu->addAction(action("object_group")); + m_contextMenu->addAction(action("object_ungroup")); + } + + } return m_contextMenu.data(); } void DefaultTool::addTransformActions(QMenu *menu) const { menu->addAction(action("object_transform_rotate_90_cw")); menu->addAction(action("object_transform_rotate_90_ccw")); menu->addAction(action("object_transform_rotate_180")); menu->addSeparator(); menu->addAction(action("object_transform_mirror_horizontally")); menu->addAction(action("object_transform_mirror_vertically")); menu->addSeparator(); menu->addAction(action("object_transform_reset")); } void DefaultTool::explicitUserStrokeEndRequest() { QList shapes = koSelection()->selectedEditableShapesAndDelegates(); emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes)); } diff --git a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp index 559b165080..5ea47a0d3b 100644 --- a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp +++ b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp @@ -1,303 +1,306 @@ /* * Copyright (c) 2017 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ToolReferenceImages.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include "ToolReferenceImagesWidget.h" #include "KisReferenceImageCollection.h" ToolReferenceImages::ToolReferenceImages(KoCanvasBase * canvas) : DefaultTool(canvas, false) { setObjectName("ToolReferenceImages"); } ToolReferenceImages::~ToolReferenceImages() { } void ToolReferenceImages::activate(ToolActivation toolActivation, const QSet &shapes) { DefaultTool::activate(toolActivation, shapes); auto kisCanvas = dynamic_cast(canvas()); connect(kisCanvas->image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), this, SLOT(slotNodeAdded(KisNodeSP))); + connect(kisCanvas->imageView()->document(), &KisDocument::sigReferenceImagesLayerChanged, this, &ToolReferenceImages::slotNodeAdded); auto referenceImageLayer = document()->referenceImagesLayer(); if (referenceImageLayer) { setReferenceImageLayer(referenceImageLayer); } } void ToolReferenceImages::deactivate() { DefaultTool::deactivate(); } void ToolReferenceImages::slotNodeAdded(KisNodeSP node) { auto *referenceImagesLayer = dynamic_cast(node.data()); if (referenceImagesLayer) { setReferenceImageLayer(referenceImagesLayer); } } void ToolReferenceImages::setReferenceImageLayer(KisSharedPtr layer) { m_layer = layer; connect(layer.data(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); } void ToolReferenceImages::addReferenceImage() { auto kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas) - KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImage"); + KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImage"); dialog.setCaption(i18n("Select a Reference Image")); QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if (!locations.isEmpty()) { dialog.setDefaultDir(locations.first()); } QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; auto *reference = KisReferenceImage::fromFile(filename, *kisCanvas->coordinatesConverter(), canvas()->canvasWidget()); if (reference) { KisDocument *doc = document(); doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference})); } } void ToolReferenceImages::pasteReferenceImage() { KisCanvas2* kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas) - KisReferenceImage* reference = KisReferenceImage::fromClipboard(*kisCanvas->coordinatesConverter()); + KisReferenceImage* reference = KisReferenceImage::fromClipboard(*kisCanvas->coordinatesConverter()); if(reference) { KisDocument *doc = document(); doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference})); } } void ToolReferenceImages::removeAllReferenceImages() { auto layer = m_layer.toStrongRef(); if (!layer) return; canvas()->addCommand(layer->removeReferenceImages(document(), layer->shapes())); } void ToolReferenceImages::loadReferenceImages() { auto kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas) - KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImageCollection"); + KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImageCollection"); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-reference-images"); dialog.setCaption(i18n("Load Reference Images")); QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if (!locations.isEmpty()) { dialog.setDefaultDir(locations.first()); } QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Could not open '%1'.", filename)); return; } KisReferenceImageCollection collection; if (collection.load(&file)) { QList shapes; Q_FOREACH(auto *reference, collection.referenceImages()) { shapes.append(reference); } KisDocument *doc = document(); doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, shapes)); } else { QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Could not load reference images from '%1'.", filename)); } file.close(); } void ToolReferenceImages::saveReferenceImages() { auto layer = m_layer.toStrongRef(); if (!layer || layer->shapeCount() == 0) return; auto kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas) - KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "SaveReferenceImageCollection"); + KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "SaveReferenceImageCollection"); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-reference-images"); dialog.setCaption(i18n("Save Reference Images")); QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if (!locations.isEmpty()) { dialog.setDefaultDir(locations.first()); } QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Could not open '%1' for saving.", filename)); return; } KisReferenceImageCollection collection(layer->referenceImages()); bool ok = collection.save(&file); file.close(); if (!ok) { QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Failed to save reference images.")); } } void ToolReferenceImages::slotSelectionChanged() { auto layer = m_layer.toStrongRef(); if (!layer) return; m_optionsWidget->selectionChanged(layer->shapeManager()->selection()); updateActions(); } QList> ToolReferenceImages::createOptionWidgets() { // Instead of inheriting DefaultTool's multi-tab implementation, inherit straight from KoToolBase return KoToolBase::createOptionWidgets(); } QWidget *ToolReferenceImages::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new ToolReferenceImagesWidget(this); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); } return m_optionsWidget; - } +} bool ToolReferenceImages::isValidForCurrentLayer() const { return true; } KoShapeManager *ToolReferenceImages::shapeManager() const { auto layer = m_layer.toStrongRef(); return layer ? layer->shapeManager() : nullptr; } KoSelection *ToolReferenceImages::koSelection() const { auto manager = shapeManager(); return manager ? manager->selection() : nullptr; } void ToolReferenceImages::updateDistinctiveActions(const QList &) { action("object_group")->setEnabled(false); action("object_unite")->setEnabled(false); action("object_intersect")->setEnabled(false); action("object_subtract")->setEnabled(false); action("object_split")->setEnabled(false); action("object_ungroup")->setEnabled(false); } void ToolReferenceImages::deleteSelection() { auto layer = m_layer.toStrongRef(); if (!layer) return; QList shapes = koSelection()->selectedShapes(); if (!shapes.empty()) { canvas()->addCommand(layer->removeReferenceImages(document(), shapes)); } } KisDocument *ToolReferenceImages::document() const { auto kisCanvas = dynamic_cast(canvas()); return kisCanvas->imageView()->document(); } QList ToolReferenceImagesFactory::createActionsImpl() { - KisActionRegistry *actionRegistry = KisActionRegistry::instance(); - QList actions = DefaultToolFactory::createActionsImpl(); - - actions << actionRegistry->makeQAction("object_order_front"); - actions << actionRegistry->makeQAction("object_order_raise"); - actions << actionRegistry->makeQAction("object_order_lower"); - actions << actionRegistry->makeQAction("object_order_back"); - actions << actionRegistry->makeQAction("object_group"); - actions << actionRegistry->makeQAction("object_ungroup"); - actions << actionRegistry->makeQAction("object_transform_rotate_90_cw"); - actions << actionRegistry->makeQAction("object_transform_rotate_90_ccw"); - actions << actionRegistry->makeQAction("object_transform_rotate_180"); - actions << actionRegistry->makeQAction("object_transform_mirror_horizontally"); - actions << actionRegistry->makeQAction("object_transform_mirror_vertically"); - actions << actionRegistry->makeQAction("object_transform_reset"); - actions << actionRegistry->makeQAction("object_unite"); - actions << actionRegistry->makeQAction("object_intersect"); - actions << actionRegistry->makeQAction("object_subtract"); - actions << actionRegistry->makeQAction("object_split"); + QList defaultActions = DefaultToolFactory::createActionsImpl(); + QList actions; + + QStringList actionNames; + actionNames << "object_order_front" + << "object_order_raise" + << "object_order_lower" + << "object_order_back" + << "object_transform_rotate_90_cw" + << "object_transform_rotate_90_ccw" + << "object_transform_rotate_180" + << "object_transform_mirror_horizontally" + << "object_transform_mirror_vertically" + << "object_transform_reset"; + + Q_FOREACH(QAction *action, defaultActions) { + if (actionNames.contains(action->objectName())) { + actions << action; + } + } return actions; } diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index 59d7c81a89..34acfb5865 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,251 +1,250 @@ /* * kis_tool_select_contiguous - part of Krayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2012 José Luis Vergara * Copyright (c) 2015 Michael Abrahams * * 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_tool_select_contiguous.h" #include #include #include #include #include #include #include #include #include #include "KoPointerEvent.h" #include "KoViewConverter.h" #include "kis_cursor.h" #include "kis_selection_manager.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "kis_layer.h" #include "kis_selection_options.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "tiles3/kis_hline_iterator.h" KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6), i18n("Contiguous Area Selection")), m_fuzziness(20), m_sizemod(0), m_feather(0), m_limitToCurrentLayer(false) { setObjectName("tool_select_contiguous"); } KisToolSelectContiguous::~KisToolSelectContiguous() { } void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolSelect::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } if (KisToolSelect::selectionDidMove()) { return; } QApplication::setOverrideCursor(KisCursor::waitCursor()); QPoint pos = convertToImagePixelCoordFloored(event); QRect rc = currentImage()->bounds(); KisFillPainter fillpainter(dev); fillpainter.setHeight(rc.height()); fillpainter.setWidth(rc.width()); fillpainter.setFillThreshold(m_fuzziness); fillpainter.setFeather(m_feather); fillpainter.setSizemod(m_sizemod); KisImageWSP image = currentImage(); KisPaintDeviceSP sourceDevice = m_limitToCurrentLayer ? dev : image->projection(); image->lock(); KisSelectionSP selection = fillpainter.createFloodSelection(pos.x(), pos.y(), sourceDevice); image->unlock(); // If we're not antialiasing, threshold the entire selection if (!antiAliasSelection()) { QRect r = selection->selectedExactRect(); if (r.isValid()) { KisHLineIteratorSP selectionIt = selection->pixelSelection()->createHLineIteratorNG(r.x(), r.y(), r.width()); for (qint32 y = 0; y < r.height(); y++) { do { if (selectionIt->rawData()[0] > 0) { selection->pixelSelection()->colorSpace()->setOpacity(selectionIt->rawData(), OPACITY_OPAQUE_U8, 1); } } while (selectionIt->nextPixel()); selectionIt->nextRow(); } } } KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas || !selection->pixelSelection()) { QApplication::restoreOverrideCursor(); return; } selection->pixelSelection()->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area")); helper.selectPixelSelection(selection->pixelSelection(), selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisToolSelectContiguous::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } void KisToolSelectContiguous::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("sizemod", sizemod); } void KisToolSelectContiguous::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("feather", feather); } QWidget* KisToolSelectContiguous::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); - selectionWidget->disableSelectionModeOption(); QVBoxLayout * l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); if (l) { QGridLayout * gridLayout = new QGridLayout(); l->insertLayout(1, gridLayout); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); gridLayout->addWidget(lbl, 0, 0, 1, 1); KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(input); input->setObjectName("fuzziness"); input->setRange(1, 100); input->setSingleStep(1); input->setExponentRatio(2); gridLayout->addWidget(input, 0, 1, 1, 1); lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget); gridLayout->addWidget(lbl, 1, 0, 1, 1); KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(sizemod); sizemod->setObjectName("sizemod"); //grow/shrink selection sizemod->setRange(-40, 40); sizemod->setSingleStep(1); gridLayout->addWidget(sizemod, 1, 1, 1, 1); lbl = new QLabel(i18n("Feathering radius: "), selectionWidget); gridLayout->addWidget(lbl, 2, 0, 1, 1); KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(feather); feather->setObjectName("feathering"); feather->setRange(0, 40); feather->setSingleStep(1); gridLayout->addWidget(feather, 2, 1, 1, 1); connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int))); connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int))); connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int))); QCheckBox* limitToCurrentLayer = new QCheckBox(i18n("Limit to current layer"), selectionWidget); l->insertWidget(4, limitToCurrentLayer); connect (limitToCurrentLayer, SIGNAL(stateChanged(int)), this, SLOT(slotLimitToCurrentLayer(int))); // load configuration settings into tool options input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink sizemod->setSuffix(i18n(" px")); feather->setValue(m_configGroup.readEntry("feather", 0)); feather->setSuffix(i18n(" px")); limitToCurrentLayer->setChecked(m_configGroup.readEntry("limitToCurrentLayer", false)); } return selectionWidget; } void KisToolSelectContiguous::slotLimitToCurrentLayer(int state) { if (state == Qt::PartiallyChecked) return; m_limitToCurrentLayer = (state == Qt::Checked); m_configGroup.writeEntry("limitToCurrentLayer", state); } void KisToolSelectContiguous::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_contiguous_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_contiguous_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.h b/plugins/tools/selectiontools/kis_tool_select_contiguous.h index 3bea53806c..68da15d936 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.h +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.h @@ -1,95 +1,97 @@ /* * kis_tool_select_contiguous.h - part of KImageShop^WKrayon^Krita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Michael Abrahams * * 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_TOOL_SELECT_CONTIGUOUS_H__ #define __KIS_TOOL_SELECT_CONTIGUOUS_H__ #include "KisSelectionToolFactoryBase.h" #include "kis_tool_select_base.h" #include #include #include /** * The 'magic wand' selection tool -- in fact just * a floodfill that only creates a selection. */ class KisToolSelectContiguous : public KisToolSelect { Q_OBJECT public: KisToolSelectContiguous(KoCanvasBase *canvas); ~KisToolSelectContiguous() override; QWidget* createOptionWidget() override; void paint(QPainter &painter, const KoViewConverter &converter) override; void beginPrimaryAction(KoPointerEvent *event) override; void resetCursorStyle() override; protected: bool wantsAutoScroll() const override { return false; } + bool isPixelOnly() const override { return true; } + public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; virtual void slotSetFuzziness(int); virtual void slotSetSizemod(int); virtual void slotSetFeather(int); virtual void slotLimitToCurrentLayer(int); //virtual bool antiAliasSelection(); protected: using KisToolSelectBase::m_widgetHelper; private: int m_fuzziness; int m_sizemod; int m_feather; bool m_limitToCurrentLayer; KConfigGroup m_configGroup; }; class KisToolSelectContiguousFactory : public KisSelectionToolFactoryBase { public: KisToolSelectContiguousFactory() : KisSelectionToolFactoryBase("KisToolSelectContiguous") { setToolTip(i18n("Contiguous Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_contiguous_selection")); setPriority(4); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectContiguousFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectContiguous(canvas); } }; #endif //__KIS_TOOL_SELECT_CONTIGUOUS_H__ diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.cc b/plugins/tools/selectiontools/kis_tool_select_outline.cc index 7b48eb24fc..43495b988d 100644 --- a/plugins/tools/selectiontools/kis_tool_select_outline.cc +++ b/plugins/tools/selectiontools/kis_tool_select_outline.cc @@ -1,282 +1,283 @@ /* * kis_tool_select_freehand.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * Copyright (c) 2015 Michael Abrahams * * 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_tool_select_outline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include "canvas/kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_algebra_2d.h" #define FEEDBACK_LINE_WIDTH 2 KisToolSelectOutline::KisToolSelectOutline(KoCanvasBase * canvas) : KisToolSelect(canvas, KisCursor::load("tool_outline_selection_cursor.png", 5, 5), i18n("Outline Selection")), m_continuedMode(false) { } KisToolSelectOutline::~KisToolSelectOutline() { } void KisToolSelectOutline::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { m_continuedMode = true; } KisToolSelect::keyPressEvent(event); } void KisToolSelectOutline::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control || !(event->modifiers() & Qt::ControlModifier)) { m_continuedMode = false; if (mode() != PAINT_MODE && !m_points.isEmpty()) { finishSelectionAction(); } } KisToolSelect::keyReleaseEvent(event); } void KisToolSelectOutline::mouseMoveEvent(KoPointerEvent *event) { KisToolSelect::mouseMoveEvent(event); if (selectionDragInProgress()) return; m_lastCursorPos = convertToPixelCoord(event); if (m_continuedMode && mode() != PAINT_MODE) { updateContinuedMode(); } } void KisToolSelectOutline::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); if (selectionDragInProgress()) return; if (!selectionEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); if (m_continuedMode && !m_points.isEmpty()) { m_paintPath.lineTo(pixelToView(convertToPixelCoord(event))); } else { m_paintPath.moveTo(pixelToView(convertToPixelCoord(event))); } m_points.append(convertToPixelCoord(event)); } void KisToolSelectOutline::continuePrimaryAction(KoPointerEvent *event) { KisToolSelectBase::continuePrimaryAction(event); if (selectionDragInProgress()) return; CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPointF point = convertToPixelCoord(event); m_paintPath.lineTo(pixelToView(point)); m_points.append(point); updateFeedback(); } void KisToolSelectOutline::endPrimaryAction(KoPointerEvent *event) { const bool hadMoveInProgress = selectionDragInProgress(); KisToolSelectBase::endPrimaryAction(event); if (hadMoveInProgress) return; CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_continuedMode) { finishSelectionAction(); + m_points.clear(); // ensure points are always cleared } } void KisToolSelectOutline::finishSelectionAction() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); const QRectF boundingRect = KisAlgebra2D::accumulateBounds(m_points); const QRectF boundingViewRect = pixelToView(boundingRect); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline")); if (helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) { return; } if (m_points.count() > 2) { QApplication::setOverrideCursor(KisCursor::waitCursor()); const SelectionMode mode = helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(), selectionMode(), selectionAction()); if (mode == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPolygon(m_points); QPainterPath cache; cache.addPolygon(m_points); cache.closeSubpath(); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_points[0])); for (int i = 1; i < m_points.count(); i++) path->lineTo(resolutionMatrix.map(m_points[i])); path->close(); path->normalize(); helper.addSelectionShape(path, selectionAction()); } QApplication::restoreOverrideCursor(); } m_points.clear(); m_paintPath = QPainterPath(); } void KisToolSelectOutline::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if ((mode() == KisTool::PAINT_MODE || m_continuedMode) && !m_points.isEmpty()) { QPainterPath outline = m_paintPath; if (m_continuedMode && mode() != KisTool::PAINT_MODE) { outline.lineTo(pixelToView(m_lastCursorPos)); } paintToolOutline(&gc, outline); } } void KisToolSelectOutline::updateFeedback() { if (m_points.count() > 1) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectOutline::updateContinuedMode() { if (!m_points.isEmpty()) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectOutline::deactivate() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); m_continuedMode = false; KisTool::deactivate(); } void KisToolSelectOutline::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_outline_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_outline_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.cc b/plugins/tools/selectiontools/kis_tool_select_similar.cc index 67d3ebe748..106d7e30e8 100644 --- a/plugins/tools/selectiontools/kis_tool_select_similar.cc +++ b/plugins/tools/selectiontools/kis_tool_select_similar.cc @@ -1,188 +1,194 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2015 Michael Abrahams * * 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_tool_select_similar.h" #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "kis_iterator_ng.h" #include "kis_image.h" void selectByColor(KisPaintDeviceSP dev, KisPixelSelectionSP selection, const quint8 *c, int fuzziness, const QRect & rc) { if (rc.isEmpty()) { return; } // XXX: Multithread this! qint32 x, y, w, h; x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); const KoColorSpace * cs = dev->colorSpace(); KisHLineConstIteratorSP hiter = dev->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = selection->createHLineIteratorNG(x, y, w); for (int row = y; row < y + h; ++row) { do { //if (dev->colorSpace()->hasAlpha()) // opacity = dev->colorSpace()->alpha(hiter->rawData()); if (fuzziness == 1) { if (memcmp(c, hiter->oldRawData(), cs->pixelSize()) == 0) { *(selIter->rawData()) = MAX_SELECTED; } } else { quint8 match = cs->difference(c, hiter->oldRawData()); if (match <= fuzziness) { *(selIter->rawData()) = MAX_SELECTED; } } } while (hiter->nextPixel() && selIter->nextPixel()); hiter->nextRow(); selIter->nextRow(); } } KisToolSelectSimilar::KisToolSelectSimilar(KoCanvasBase * canvas) : KisToolSelect(canvas, KisCursor::load("tool_similar_selection_cursor.png", 6, 6), i18n("Similar Color Selection")), m_fuzziness(20) { } void KisToolSelectSimilar::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolSelect::activate(toolActivation, shapes); + if (selectionOptionWidget()) { + // similar color selection tool doesn't use antialiasing option for now + // hence explicit disabling it + selectionOptionWidget()->disableAntiAliasSelectionOption(); + } m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectSimilar::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } if (KisToolSelect::selectionDidMove()) { return; } QPointF pos = convertToPixelCoord(event); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); QApplication::setOverrideCursor(KisCursor::waitCursor()); KoColor c; dev->pixel(pos.x(), pos.y(), &c); // XXX we should make this configurable: "allow to select transparent" // if (opacity > OPACITY_TRANSPARENT) KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); QRect rc; if (dev->colorSpace()->difference(c.data(), dev->defaultPixel().data()) <= m_fuzziness) { rc = image()->bounds(); } else { rc = dev->exactBounds(); } selectByColor(dev, tmpSel, c.data(), m_fuzziness, rc); tmpSel->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Similar Color")); helper.selectPixelSelection(tmpSel, selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectSimilar::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } QWidget* KisToolSelectSimilar::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); + // similar color selection tool doesn't use antialiasing option for now + // hence explicit disabling it selectionWidget->disableAntiAliasSelectionOption(); - selectionWidget->disableSelectionModeOption(); QHBoxLayout* fl = new QHBoxLayout(); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); fl->addWidget(lbl); KisSliderSpinBox* input = new KisSliderSpinBox(selectionWidget); input->setObjectName("fuzziness"); - input->setRange(0, 200); + input->setRange(1, 200); input->setSingleStep(10); fl->addWidget(input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int))); QVBoxLayout* l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); l->insertLayout(1, fl); // load setting from config input->setValue(m_configGroup.readEntry("fuzziness", 20)); return selectionWidget; } void KisToolSelectSimilar::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_similar_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_similar_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.h b/plugins/tools/selectiontools/kis_tool_select_similar.h index 06207ffdb3..c02fa766b7 100644 --- a/plugins/tools/selectiontools/kis_tool_select_similar.h +++ b/plugins/tools/selectiontools/kis_tool_select_similar.h @@ -1,75 +1,76 @@ /* * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2015 Michael Abrahams * * 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_TOOL_SELECT_SIMILAR_H_ #define KIS_TOOL_SELECT_SIMILAR_H_ #include #include #include #include "kis_tool_select_base.h" #include /* * Tool to select colors by pointing at a color on the image. */ class KisToolSelectSimilar: public KisToolSelect { Q_OBJECT public: KisToolSelectSimilar(KoCanvasBase * canvas); void beginPrimaryAction(KoPointerEvent *event) override; void paint(QPainter&, const KoViewConverter &) override {} QWidget* createOptionWidget() override; void resetCursorStyle() override; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void slotSetFuzziness(int); protected: using KisToolSelectBase::m_widgetHelper; + bool isPixelOnly() const override { return true; } private: int m_fuzziness; KConfigGroup m_configGroup; }; class KisToolSelectSimilarFactory : public KisSelectionToolFactoryBase { public: KisToolSelectSimilarFactory() : KisSelectionToolFactoryBase("KisToolSelectSimilar") { setToolTip(i18n("Similar Color Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_similar_selection")); setPriority(5); } ~KisToolSelectSimilarFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectSimilar(canvas); } }; #endif // KIS_TOOL_SELECT_SIMILAR_H_ diff --git a/plugins/tools/tool_crop/kis_tool_crop.cc b/plugins/tools/tool_crop/kis_tool_crop.cc index a35bd33e74..42d928d102 100644 --- a/plugins/tools/tool_crop/kis_tool_crop.cc +++ b/plugins/tools/tool_crop/kis_tool_crop.cc @@ -1,914 +1,917 @@ /* * kis_tool_crop.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Michael Thaler * Copyright (c) 2006 Cyrille Berger * Copyright (C) 2007 Adrian Page * * 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_tool_crop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct DecorationLine { QPointF start; QPointF end; enum Relation { Width, Height, Smallest, Largest }; Relation startXRelation; Relation startYRelation; Relation endXRelation; Relation endYRelation; }; DecorationLine decors[20] = { //thirds {QPointF(0.0, 0.3333),QPointF(1.0, 0.3333), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.0, 0.6666),QPointF(1.0, 0.6666), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.3333, 0.0),QPointF(0.3333, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.6666, 0.0),QPointF(0.6666, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, //fifths {QPointF(0.0, 0.2),QPointF(1.0, 0.2), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.0, 0.4),QPointF(1.0, 0.4), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.0, 0.6),QPointF(1.0, 0.6), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.0, 0.8),QPointF(1.0, 0.8), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.2, 0.0),QPointF(0.2, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.4, 0.0),QPointF(0.4, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.6, 0.0),QPointF(0.6, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.8, 0.0),QPointF(0.8, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, // Passport photo {QPointF(0.0, 0.45/0.35),QPointF(1.0, 0.45/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width}, {QPointF(0.2, 0.05/0.35),QPointF(0.8, 0.05/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width}, {QPointF(0.2, 0.40/0.35),QPointF(0.8, 0.40/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width}, {QPointF(0.25, 0.07/0.35),QPointF(0.75, 0.07/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width}, {QPointF(0.25, 0.38/0.35),QPointF(0.75, 0.38/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width}, {QPointF(0.35/0.45, 0.0),QPointF(0.35/0.45, 1.0), DecorationLine::Height, DecorationLine::Height, DecorationLine::Height, DecorationLine::Height}, //Crosshair {QPointF(0.0, 0.5),QPointF(1.0, 0.5), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}, {QPointF(0.5, 0.0),QPointF(0.5, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height} }; #define DECORATION_COUNT 5 const int decorsIndex[DECORATION_COUNT] = {0,4,12,18,20}; KisToolCrop::KisToolCrop(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::load("tool_crop_cursor.png", 6, 6)) { setObjectName("tool_crop"); m_handleSize = 13; m_haveCropSelection = false; m_cropTypeSelectable = false; m_cropType = ImageCropType; m_decoration = 1; connect(&m_finalRect, SIGNAL(sigValuesChanged()), SLOT(slotRectChanged())); connect(&m_finalRect, SIGNAL(sigLockValuesChanged()), SLOT(slotRectChanged())); // context menu options (mirrors tool options) m_contextMenu.reset(new QMenu()); applyCrop = new KisAction(i18n("Crop")); growToggleOption = new KisAction(i18n("Grow")); growToggleOption->setCheckable(true); centerToggleOption = new KisAction(i18n("Center")); centerToggleOption->setCheckable(true); } KisToolCrop::~KisToolCrop() { } void KisToolCrop::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); configGroup = KSharedConfig::openConfig()->group(toolId()); // save settings to kritarc KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); // load settings from configuration setGrowCenter(configGroup.readEntry("growCenter", false)); setAllowGrow(configGroup.readEntry("allowGrow", false)); // Default: thirds decoration setDecoration(configGroup.readEntry("decoration", 1)); // Default: crop the entire image setCropType(configGroup.readEntry("cropType", 1) == 0 ? LayerCropType : ImageCropType); m_finalRect.setCropRect(image()->bounds()); KisSelectionSP sel = resources->activeSelection(); if (sel) { m_haveCropSelection = true; m_finalRect.setRectInitial(sel->selectedExactRect()); } useCursor(cursor()); //pixel layer if(resources->currentNode() && resources->currentNode()->paintDevice()) { setCropTypeSelectable(true); } //vector layer else { setCropTypeSelectable(false); } } void KisToolCrop::cancelStroke() { m_haveCropSelection = false; doCanvasUpdate(image()->bounds()); } void KisToolCrop::deactivate() { cancelStroke(); KisTool::deactivate(); } void KisToolCrop::requestStrokeEnd() { if (m_haveCropSelection) crop(); } void KisToolCrop::requestStrokeCancellation() { cancelStroke(); } void KisToolCrop::canvasResourceChanged(int key, const QVariant &res) { KisTool::canvasResourceChanged(key, res); //pixel layer if(currentNode() && currentNode()->paintDevice()) { setCropTypeSelectable(true); } //vector layer else { setCropType(ImageCropType); setCropTypeSelectable(false); } } void KisToolCrop::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); paintOutlineWithHandles(painter); } QMenu *KisToolCrop::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); + m_contextMenu->addSection(i18n("Crop Tool Actions")); + m_contextMenu->addSeparator(); + // keeps in sync with tool options growToggleOption->setChecked(allowGrow()); centerToggleOption->setChecked(growCenter()); if (m_haveCropSelection) { // can't crop if there is no selection m_contextMenu->addAction(applyCrop); m_contextMenu->addSeparator(); } m_contextMenu->addAction(growToggleOption); m_contextMenu->addAction(centerToggleOption); } return m_contextMenu.data(); } void KisToolCrop::beginPrimaryAction(KoPointerEvent *event) { m_finalRect.setCropRect(image()->bounds()); setMode(KisTool::PAINT_MODE); const QPointF imagePoint = convertToPixelCoord(event); m_mouseOnHandleType = mouseOnHandle(pixelToView(imagePoint)); if (m_mouseOnHandleType != KisConstrainedRect::None) { QPointF snapPoint = m_finalRect.handleSnapPoint(KisConstrainedRect::HandleType(m_mouseOnHandleType), imagePoint); QPointF snapDocPoint = image()->pixelToDocument(snapPoint); m_dragOffsetDoc = snapDocPoint - event->point; } else { m_dragOffsetDoc = QPointF(); } QPointF snappedPoint = convertToPixelCoordAndSnap(event, m_dragOffsetDoc); m_dragStart = snappedPoint.toPoint(); m_resettingStroke = false; if (!m_haveCropSelection || m_mouseOnHandleType == None) { m_lastCanvasUpdateRect = image()->bounds(); const int initialWidth = m_finalRect.widthLocked() ? m_finalRect.rect().width() : 1; const int initialHeight = m_finalRect.heightLocked() ? m_finalRect.rect().height() : 1; const QRect initialRect = QRect(m_dragStart, QSize(initialWidth, initialHeight)); m_finalRect.setRectInitial(initialRect); m_initialDragRect = initialRect; m_mouseOnHandleType = KisConstrainedRect::Creation; m_resettingStroke = true; } else { m_initialDragRect = m_finalRect.rect(); } } void KisToolCrop::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); const QPointF pos = convertToPixelCoordAndSnap(event, m_dragOffsetDoc); const QPoint drag = pos.toPoint() - m_dragStart; m_finalRect.moveHandle(KisConstrainedRect::HandleType(m_mouseOnHandleType), drag, m_initialDragRect); } bool KisToolCrop::tryContinueLastCropAction() { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); const KisCropSavedExtraData *data = 0; if ((lastCommand = image()->undoAdapter()->presentCommand()) && (data = dynamic_cast(lastCommand->extraData()))) { bool cropImageConsistent = m_cropType == ImageCropType && (data->type() == KisCropSavedExtraData::CROP_IMAGE || data->type() == KisCropSavedExtraData::RESIZE_IMAGE); bool cropLayerConsistent = m_cropType == LayerCropType && data->type() == KisCropSavedExtraData::CROP_LAYER && currentNode() == data->cropNode(); if (cropImageConsistent || cropLayerConsistent) { image()->undoAdapter()->undoLastCommand(); image()->waitForDone(); m_finalRect.setRectInitial(data->cropRect()); m_haveCropSelection = true; result = true; } } return result; } void KisToolCrop::endPrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); QRectF viewCropRect = pixelToView(m_finalRect.rect()); const bool haveValidRect = viewCropRect.width() > m_handleSize && viewCropRect.height() > m_handleSize; if (!m_haveCropSelection && !haveValidRect) { if (!tryContinueLastCropAction()) { m_finalRect.setRectInitial(image()->bounds()); m_haveCropSelection = true; } } else if (m_resettingStroke && !haveValidRect) { m_lastCanvasUpdateRect = image()->bounds(); m_haveCropSelection = false; } else { m_haveCropSelection = true; } m_finalRect.normalize(); qint32 type = mouseOnHandle(pixelToView(convertToPixelCoordAndSnap(event, m_dragOffsetDoc))); setMoveResizeCursor(type); } void KisToolCrop::mouseMoveEvent(KoPointerEvent *event) { QPointF pos = convertToPixelCoordAndSnap(event); if (m_haveCropSelection) { //if the crop selection is set //set resize cursor if we are on one of the handles if(mode() == KisTool::PAINT_MODE) { //keep the same cursor as the one we clicked with setMoveResizeCursor(m_mouseOnHandleType); }else{ //hovering qint32 type = mouseOnHandle(pixelToView(pos)); setMoveResizeCursor(type); } } } void KisToolCrop::beginPrimaryDoubleClickAction(KoPointerEvent *event) { if (m_haveCropSelection) crop(); // this action will have no continuation event->ignore(); } #define BORDER_LINE_WIDTH 0 #define HALF_BORDER_LINE_WIDTH 0 #define HANDLE_BORDER_LINE_WIDTH 1 QRectF KisToolCrop::borderLineRect() { QRectF borderRect = pixelToView(m_finalRect.rect()); // Draw the border line right next to the crop rectangle perimeter. borderRect.adjust(-HALF_BORDER_LINE_WIDTH, -HALF_BORDER_LINE_WIDTH, HALF_BORDER_LINE_WIDTH, HALF_BORDER_LINE_WIDTH); return borderRect; } #define OUTSIDE_CROP_ALPHA 200 void KisToolCrop::paintOutlineWithHandles(QPainter& gc) { if (canvas() && (mode() == KisTool::PAINT_MODE || m_haveCropSelection)) { gc.save(); QRectF wholeImageRect = pixelToView(image()->bounds()); QRectF borderRect = borderLineRect(); QPainterPath path; path.addRect(wholeImageRect); path.addRect(borderRect); gc.setPen(Qt::NoPen); gc.setBrush(QColor(0, 0, 0, OUTSIDE_CROP_ALPHA)); gc.drawPath(path); // Handles QPen pen(Qt::SolidLine); pen.setWidth(HANDLE_BORDER_LINE_WIDTH); pen.setColor(Qt::black); gc.setPen(pen); gc.setBrush(QColor(200, 200, 200, OUTSIDE_CROP_ALPHA)); gc.drawPath(handlesPath()); gc.setClipRect(borderRect, Qt::IntersectClip); if (m_decoration > 0) { for (int i = decorsIndex[m_decoration-1]; ipaintDevice()) { currentImage()->cropNode(currentNode(), cropRect); } else { currentImage()->cropImage(cropRect); } } void KisToolCrop::setCropTypeLegacy(int cropType) { setCropType(cropType == 0 ? LayerCropType : ImageCropType); } void KisToolCrop::setCropType(KisToolCrop::CropToolType cropType) { if(m_cropType == cropType) return; m_cropType = cropType; // can't save LayerCropType, so have to convert it to int for saving configGroup.writeEntry("cropType", cropType == LayerCropType ? 0 : 1); emit cropTypeChanged(m_cropType); } KisToolCrop::CropToolType KisToolCrop::cropType() const { return m_cropType; } void KisToolCrop::setCropTypeSelectable(bool selectable) { if(selectable == m_cropTypeSelectable) return; m_cropTypeSelectable = selectable; emit cropTypeSelectableChanged(); } bool KisToolCrop::cropTypeSelectable() const { return m_cropTypeSelectable; } int KisToolCrop::decoration() const { return m_decoration; } void KisToolCrop::setDecoration(int i) { // This shouldn't happen, but safety first if(i < 0 || i > DECORATION_COUNT) return; m_decoration = i; emit decorationChanged(decoration()); updateCanvasViewRect(boundingRect()); configGroup.writeEntry("decoration", i); } void KisToolCrop::doCanvasUpdate(const QRect &updateRect) { updateCanvasViewRect(updateRect | m_lastCanvasUpdateRect); m_lastCanvasUpdateRect = updateRect; } void KisToolCrop::slotRectChanged() { emit cropHeightChanged(cropHeight()); emit cropWidthChanged(cropWidth()); emit cropXChanged(cropX()); emit cropYChanged(cropY()); emit ratioChanged(ratio()); emit forceHeightChanged(forceHeight()); emit forceWidthChanged(forceWidth()); emit forceRatioChanged(forceRatio()); emit canGrowChanged(allowGrow()); emit isCenteredChanged(growCenter()); doCanvasUpdate(boundingRect().toAlignedRect()); } void KisToolCrop::setCropX(int x) { if(x == m_finalRect.rect().x()) return; if (!m_haveCropSelection) { m_haveCropSelection = true; m_finalRect.setRectInitial(image()->bounds()); } QPoint offset = m_finalRect.rect().topLeft(); offset.setX(x); m_finalRect.setOffset(offset); } int KisToolCrop::cropX() const { return m_finalRect.rect().x(); } void KisToolCrop::setCropY(int y) { if(y == m_finalRect.rect().y()) return; if (!m_haveCropSelection) { m_haveCropSelection = true; m_finalRect.setRectInitial(image()->bounds()); } QPoint offset = m_finalRect.rect().topLeft(); offset.setY(y); m_finalRect.setOffset(offset); } int KisToolCrop::cropY() const { return m_finalRect.rect().y(); } void KisToolCrop::setCropWidth(int w) { if(w == m_finalRect.rect().width()) return; if (!m_haveCropSelection) { m_haveCropSelection = true; m_finalRect.setRectInitial(image()->bounds()); } m_finalRect.setWidth(w); } int KisToolCrop::cropWidth() const { return m_finalRect.rect().width(); } void KisToolCrop::setForceWidth(bool force) { m_finalRect.setWidthLocked(force); } bool KisToolCrop::forceWidth() const { return m_finalRect.widthLocked(); } void KisToolCrop::setCropHeight(int h) { if(h == m_finalRect.rect().height()) return; if (!m_haveCropSelection) { m_haveCropSelection = true; m_finalRect.setRectInitial(image()->bounds()); } m_finalRect.setHeight(h); } int KisToolCrop::cropHeight() const { return m_finalRect.rect().height(); } void KisToolCrop::setForceHeight(bool force) { m_finalRect.setHeightLocked(force); } bool KisToolCrop::forceHeight() const { return m_finalRect.heightLocked(); } void KisToolCrop::setAllowGrow(bool g) { m_finalRect.setCanGrow(g); m_finalRect.setCropRect(image()->bounds()); configGroup.writeEntry("allowGrow", g); emit canGrowChanged(g); } bool KisToolCrop::allowGrow() const { return m_finalRect.canGrow(); } void KisToolCrop::setGrowCenter(bool value) { m_finalRect.setCentered(value); configGroup.writeEntry("growCenter", value); emit isCenteredChanged(value); } bool KisToolCrop::growCenter() const { return m_finalRect.centered(); } void KisToolCrop::setRatio(double ratio) { if(ratio == m_finalRect.ratio()) return; if (!m_haveCropSelection) { m_haveCropSelection = true; m_finalRect.setRectInitial(image()->bounds()); } m_finalRect.setRatio(ratio); } double KisToolCrop::ratio() const { return m_finalRect.ratio(); } void KisToolCrop::setForceRatio(bool force) { m_finalRect.setRatioLocked(force); } bool KisToolCrop::forceRatio() const { return m_finalRect.ratioLocked(); } QWidget* KisToolCrop::createOptionWidget() { optionsWidget = new KisToolCropConfigWidget(0, this); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); optionsWidget->layout()->addWidget(specialSpacer); Q_CHECK_PTR(optionsWidget); optionsWidget->setObjectName(toolId() + " option widget"); connect(optionsWidget->bnCrop, SIGNAL(clicked()), this, SLOT(crop())); connect(optionsWidget, SIGNAL(cropTypeChanged(int)), this, SLOT(setCropTypeLegacy(int))); connect(optionsWidget, SIGNAL(cropXChanged(int)), this, SLOT(setCropX(int))); connect(optionsWidget, SIGNAL(cropYChanged(int)), this, SLOT(setCropY(int))); connect(optionsWidget, SIGNAL(cropHeightChanged(int)), this, SLOT(setCropHeight(int))); connect(optionsWidget, SIGNAL(forceHeightChanged(bool)), this, SLOT(setForceHeight(bool))); connect(optionsWidget, SIGNAL(cropWidthChanged(int)), this, SLOT(setCropWidth(int))); connect(optionsWidget, SIGNAL(forceWidthChanged(bool)), this, SLOT(setForceWidth(bool))); connect(optionsWidget, SIGNAL(ratioChanged(double)), this, SLOT(setRatio(double))); connect(optionsWidget, SIGNAL(forceRatioChanged(bool)), this, SLOT(setForceRatio(bool))); connect(optionsWidget, SIGNAL(decorationChanged(int)), this, SLOT(setDecoration(int))); connect(optionsWidget, SIGNAL(allowGrowChanged(bool)), this, SLOT(setAllowGrow(bool))); connect(optionsWidget, SIGNAL(growCenterChanged(bool)), this, SLOT(setGrowCenter(bool))); optionsWidget->setFixedHeight(optionsWidget->sizeHint().height()); connect(applyCrop, SIGNAL(triggered(bool)), this, SLOT(crop())); connect(growToggleOption, SIGNAL(triggered(bool)), this, SLOT(setAllowGrow(bool))); connect(centerToggleOption, SIGNAL(triggered(bool)), this, SLOT(setGrowCenter(bool))); return optionsWidget; } QRectF KisToolCrop::lowerRightHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.right() - m_handleSize / 2.0, cropBorderRect.bottom() - m_handleSize / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::upperRightHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.right() - m_handleSize / 2.0 , cropBorderRect.top() - m_handleSize / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::lowerLeftHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.left() - m_handleSize / 2.0 , cropBorderRect.bottom() - m_handleSize / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::upperLeftHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.left() - m_handleSize / 2.0, cropBorderRect.top() - m_handleSize / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::lowerHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.left() + (cropBorderRect.width() - m_handleSize) / 2.0 , cropBorderRect.bottom() - m_handleSize / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::rightHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.right() - m_handleSize / 2.0 , cropBorderRect.top() + (cropBorderRect.height() - m_handleSize) / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::upperHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.left() + (cropBorderRect.width() - m_handleSize) / 2.0 , cropBorderRect.top() - m_handleSize / 2.0, m_handleSize, m_handleSize); } QRectF KisToolCrop::leftHandleRect(QRectF cropBorderRect) { return QRectF(cropBorderRect.left() - m_handleSize / 2.0, cropBorderRect.top() + (cropBorderRect.height() - m_handleSize) / 2.0, m_handleSize, m_handleSize); } QPainterPath KisToolCrop::handlesPath() { QRectF cropBorderRect = borderLineRect(); QPainterPath path; path.addRect(upperLeftHandleRect(cropBorderRect)); path.addRect(upperRightHandleRect(cropBorderRect)); path.addRect(lowerLeftHandleRect(cropBorderRect)); path.addRect(lowerRightHandleRect(cropBorderRect)); path.addRect(upperHandleRect(cropBorderRect)); path.addRect(lowerHandleRect(cropBorderRect)); path.addRect(leftHandleRect(cropBorderRect)); path.addRect(rightHandleRect(cropBorderRect)); return path; } qint32 KisToolCrop::mouseOnHandle(QPointF currentViewPoint) { QRectF borderRect = borderLineRect(); qint32 handleType = None; if (!m_haveCropSelection) { return None; } if (upperLeftHandleRect(borderRect).contains(currentViewPoint)) { handleType = UpperLeft; } else if (lowerLeftHandleRect(borderRect).contains(currentViewPoint)) { handleType = LowerLeft; } else if (upperRightHandleRect(borderRect).contains(currentViewPoint)) { handleType = UpperRight; } else if (lowerRightHandleRect(borderRect).contains(currentViewPoint)) { handleType = LowerRight; } else if (upperHandleRect(borderRect).contains(currentViewPoint)) { handleType = Upper; } else if (lowerHandleRect(borderRect).contains(currentViewPoint)) { handleType = Lower; } else if (leftHandleRect(borderRect).contains(currentViewPoint)) { handleType = Left; } else if (rightHandleRect(borderRect).contains(currentViewPoint)) { handleType = Right; } else if (borderRect.contains(currentViewPoint)) { handleType = Inside; } return handleType; } void KisToolCrop::setMoveResizeCursor(qint32 handle) { QCursor cursor; switch (handle) { case(UpperLeft): case(LowerRight): cursor = KisCursor::sizeFDiagCursor(); break; case(LowerLeft): case(UpperRight): cursor = KisCursor::sizeBDiagCursor(); break; case(Upper): case(Lower): cursor = KisCursor::sizeVerCursor(); break; case(Left): case(Right): cursor = KisCursor::sizeHorCursor(); break; case(Inside): cursor = KisCursor::sizeAllCursor(); break; default: cursor = KisCursor::arrowCursor(); break; } useCursor(cursor); } QRectF KisToolCrop::boundingRect() { QRectF rect = handlesPath().boundingRect(); rect.adjust(-HANDLE_BORDER_LINE_WIDTH, -HANDLE_BORDER_LINE_WIDTH, HANDLE_BORDER_LINE_WIDTH, HANDLE_BORDER_LINE_WIDTH); return rect; } void KisToolCrop::drawDecorationLine(QPainter *p, DecorationLine *decorLine, const QRectF rect) { QPointF start = rect.topLeft(); QPointF end = rect.topLeft(); qreal small = qMin(rect.width(), rect.height()); qreal large = qMax(rect.width(), rect.height()); switch (decorLine->startXRelation) { case DecorationLine::Width: start.setX(start.x() + decorLine->start.x() * rect.width()); break; case DecorationLine::Height: start.setX(start.x() + decorLine->start.x() * rect.height()); break; case DecorationLine::Smallest: start.setX(start.x() + decorLine->start.x() * small); break; case DecorationLine::Largest: start.setX(start.x() + decorLine->start.x() * large); break; } switch (decorLine->startYRelation) { case DecorationLine::Width: start.setY(start.y() + decorLine->start.y() * rect.width()); break; case DecorationLine::Height: start.setY(start.y() + decorLine->start.y() * rect.height()); break; case DecorationLine::Smallest: start.setY(start.y() + decorLine->start.y() * small); break; case DecorationLine::Largest: start.setY(start.y() + decorLine->start.y() * large); break; } switch (decorLine->endXRelation) { case DecorationLine::Width: end.setX(end.x() + decorLine->end.x() * rect.width()); break; case DecorationLine::Height: end.setX(end.x() + decorLine->end.x() * rect.height()); break; case DecorationLine::Smallest: end.setX(end.x() + decorLine->end.x() * small); break; case DecorationLine::Largest: end.setX(end.x() + decorLine->end.x() * large); break; } switch (decorLine->endYRelation) { case DecorationLine::Width: end.setY(end.y() + decorLine->end.y() * rect.width()); break; case DecorationLine::Height: end.setY(end.y() + decorLine->end.y() * rect.height()); break; case DecorationLine::Smallest: end.setY(end.y() + decorLine->end.y() * small); break; case DecorationLine::Largest: end.setY(end.y() + decorLine->end.y() * large); break; } p->drawLine(start, end); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index ab6095961f..5545dc4950 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1320 +1,1322 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * 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 "kis_tool_transform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "krita_container_utils.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); + m_contextMenu->addSection(i18n("Transform Tool Actions")); + m_contextMenu->addSeparator(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitArgsFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->transformArgs(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode, &oldTransformedNodes) && args->mode() == mode && oldRootNode == currentNode) { KisNodeList perspectiveTransformedNodes = fetchNodesList(mode, currentNode, m_workRecursively); if (KritaUtils::compareListsUnordered(oldTransformedNodes, perspectiveTransformedNodes)) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); forceRepaintDelayedLayers(oldRootNode); result = true; } } return result; } void KisToolTransform::resetArgsForMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { resetArgsForMode(mode); initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else if (m_selectedPortionCache) { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (m_selectedPortionCache) { if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (!blockUntilOperationsFinished()) { return; } // set up and null checks before we do anything KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) return; // some layer types cannot be transformed. Give a message and return if a user tries it if (currentNode->inherits("KisColorizeMask") || currentNode->inherits("KisFileLayer") || currentNode->inherits("KisCloneLayer")) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Layer type cannot use the transform tool"), koIcon("object-locked"), 4000, KisFloatingMessage::High); // force-reset transform mode to default initTransformMode(mode); return; } // this goes after the floating message check since the fetchNodesList() // says a file layer is "empty" and messes up the floating message check above QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); if (nodesList.isEmpty()) return; /** * We must ensure that the currently selected subtree * has finished all its updates. */ forceRepaintDelayedLayers(currentNode); ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } KisSelectionSP selection = resources->activeSelection(); /** * When working with transform mask, selections are not * taken into account. */ if (selection && dynamic_cast(currentNode.data())) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); selection = 0; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, nodesList, selection, image().data()); connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP))); QRect srcRect; if (selection) { srcRect = selection->selectedExactRect(); } else { srcRect = QRect(); Q_FOREACH (KisNodeSP node, nodesList) { // group layers may have a projection of layers // that are locked and will not be transformed if (node->inherits("KisGroupLayer")) continue; if (const KisTransformMask *mask = dynamic_cast(node.data())) { srcRect |= mask->sourceDataBounds(); } else { srcRect |= node->exactBounds(); } } } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); if (!forceReset && fetchedFromCommand) { m_currentArgs = fetchedArgs; } else if (forceReset || !tryInitArgsFromNode(currentNode)) { resetArgsForMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::PreparePreviewData()); bool haveInvisibleNodes = clearDevices(nodesList); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); slotPreviewDeviceGenerated(0); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformClearedDevices(); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); // root node is used for progress only image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device) { if (device && device->exactBounds().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); cancelStroke(); } else { initThumbnailImage(device); updateSelectionPath(); initGuiAfterTransformMode(); } } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; m_changesTracker.commitConfig(toQShared(m_currentArgs.clone())); } void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status) { const ToolTransformArgs *newArgs = dynamic_cast(status.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs); *m_transaction.currentConfig() = *newArgs; slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool KisToolTransform::clearDevices(const QList &nodes) { bool haveInvisibleNodes = false; Q_FOREACH (KisNodeSP node, nodes) { haveInvisibleNodes |= !node->visible(false); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); } return haveInvisibleNodes; } void KisToolTransform::transformClearedDevices() { Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(node); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, node)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) { KIS_SAFE_ASSERT_RECOVER_RETURN(root); KisLayerUtils::forceAllDelayedNodesUpdate(root); image()->waitForDone(); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h index 52fcd6b552..e7cb9165c0 100644 --- a/sdk/tests/filestest.h +++ b/sdk/tests/filestest.h @@ -1,369 +1,377 @@ /* * Copyright (C) 2007 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. */ #ifndef FILESTEST #define FILESTEST #include "testutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace TestUtil { void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true) { QDir dirSources(_dirname); QStringList failuresFileInfo; QStringList failuresDocImage; QStringList failuresCompare; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { qDebug() << sourceFileInfo.fileName(); if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) { continue; } if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) { QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png"); if (!resultFileInfo.exists()) { failuresFileInfo << resultFileInfo.fileName(); continue; } KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); if (!doc->image()) { failuresDocImage << sourceFileInfo.fileName(); continue; } QString id = doc->image()->colorSpace()->id(); if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") { dbgKrita << "Images need conversion"; doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::NoOptimization); } qApp->processEvents(); doc->image()->waitForDone(); QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QImage resultImage(resultFileInfo.absoluteFilePath()); resultImage = resultImage.convertToFormat(QImage::Format_ARGB32); sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32); QPoint pt; if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) { failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1(); sourceImage.save(sourceFileInfo.fileName() + ".png"); resultImage.save(resultFileInfo.fileName() + ".expected.png"); continue; } delete doc; } } if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) { return; } qWarning() << "Comparison failures: " << failuresCompare; qWarning() << "No image failures: " << failuresDocImage; qWarning() << "No comparison image: " << failuresFileInfo; QFAIL("Failed testing files"); } void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead) { QFileDevice::Permissions permissionsBefore; if (sourceFileInfo.exists()) { permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath()); ENTER_FUNCTION() << permissionsBefore; } else { QFile file(sourceFileInfo.absoluteFilePath()); bool opened = file.open(QIODevice::ReadWrite); if (!opened) { qDebug() << "The file cannot be opened/created: " << file.error() << file.errorString(); } permissionsBefore = file.permissions(); file.close(); } QFileDevice::Permissions permissionsNow = permissionsBefore; if (removePermissionToRead) { permissionsNow = permissionsBefore & (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther); } if (removePermissionToWrite) { permissionsNow = permissionsBefore & (~QFileDevice::WriteUser & ~QFileDevice::WriteOwner & ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther); } QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow); } void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo) { QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions(); QFileDevice::Permissions permissionsAfter = permissionsNow | (QFileDevice::ReadUser | QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther) | (QFileDevice::WriteUser | QFileDevice::WriteOwner | QFileDevice::WriteGroup | QFileDevice::WriteOther); QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter); } void testImportFromWriteonly(const QString& _dirname, QString mimetype = "") { QString writeonlyFilename = _dirname + "writeonlyFile.txt"; QFileInfo sourceFileInfo(writeonlyFilename); prepareFile(sourceFileInfo, false, true); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); qDebug() << "import result = " << status; QString failMessage = ""; bool fail = false; if (status == ImportExportCodes::FileFormatIncorrect) { qDebug() << "Make sure you set the correct mimetype in the test case."; failMessage = "Incorrect status."; fail = true; } qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } delete doc; restorePermissionsToReadAndWrite(sourceFileInfo); QVERIFY(!status.isOk()); if (fail) { QFAIL(failMessage.toUtf8()); } } void testExportToReadonly(const QString& _dirname, QString mimetype = "", bool useDocumentExport=false) { QString readonlyFilename = _dirname + "readonlyFile.txt"; QFileInfo sourceFileInfo(readonlyFilename); prepareFile(sourceFileInfo, true, false); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = ImportExportCodes::OK; QString failMessage = ""; bool fail = false; { MaskParent p; ENTER_FUNCTION() << doc->image(); doc->setCurrentImage(p.image); if (useDocumentExport) { bool result = doc->exportDocumentSync(QUrl(sourceFileInfo.absoluteFilePath()), mimetype.toUtf8()); status = result ? ImportExportCodes::OK : ImportExportCodes::Failure; } else { status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8()); } qDebug() << "export result = " << status; if (!useDocumentExport && status == ImportExportCodes::FileFormatIncorrect) { qDebug() << "Make sure you set the correct mimetype in the test case."; failMessage = "Incorrect status."; fail = true; } qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } } delete doc; restorePermissionsToReadAndWrite(sourceFileInfo); QVERIFY(!status.isOk()); if (fail) { QFAIL(failMessage.toUtf8()); } } void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "") { QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt"; QFileInfo sourceFileInfo(incorrectFormatFilename); prepareFile(sourceFileInfo, false, false); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); qDebug() << "import result = " << status; qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } delete doc; QVERIFY(!status.isOk()); QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect) || status == KisImportExportErrorCode(ImportExportCodes::ErrorWhileReading)); // in case the filter doesn't know if it can't read or just parse } void testExportToColorSpace(const QString& _dirname, QString mimetype, const KoColorSpace* space, KisImportExportErrorCode expected, bool useDocumentExport=false) { QString colorspaceFilename = _dirname + "colorspace.txt"; QFileInfo sourceFileInfo(colorspaceFilename); prepareFile(sourceFileInfo, true, true); restorePermissionsToReadAndWrite(sourceFileInfo); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode statusExport = ImportExportCodes::OK; KisImportExportErrorCode statusImport = ImportExportCodes::OK; QString failMessage = ""; bool fail = false; { MaskParent p; doc->setCurrentImage(p.image); doc->image()->convertImageColorSpace(space, KoColorConversionTransformation::Intent::IntentPerceptual, KoColorConversionTransformation::ConversionFlag::Empty); if (useDocumentExport) { bool result = doc->exportDocumentSync(QUrl(QString("file:") + QString(colorspaceFilename)), mimetype.toUtf8()); statusExport = result ? ImportExportCodes::OK : ImportExportCodes::Failure; } else { statusExport = manager.exportDocument(colorspaceFilename, colorspaceFilename, mimetype.toUtf8()); } statusImport = manager.importDocument(colorspaceFilename, mimetype.toUtf8()); - QVERIFY(statusImport == ImportExportCodes::OK); - if (*(doc->image()->colorSpace()) != *space) { + if (!(statusImport == ImportExportCodes::OK)) { + fail = true; + failMessage = "Incorrect status"; + } + + bool mismatch = (*(doc->image()->colorSpace()) != *space) || (doc->image()->colorSpace()->profile() != space->profile()); + if (mismatch) { qDebug() << "Document color space = " << (doc->image()->colorSpace())->id(); qDebug() << "Saved color space = " << space->id(); - QVERIFY(*(doc->image()->colorSpace()) == *space); + fail = true; + failMessage = "Mismatch of color spaces"; } if (!useDocumentExport && statusExport == ImportExportCodes::FileFormatIncorrect) { qDebug() << "Make sure you set the correct mimetype in the test case."; failMessage = "Incorrect status."; fail = true; } qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } } delete doc; QFile::remove(colorspaceFilename); + if (fail) { + QFAIL(failMessage.toUtf8()); + } + QVERIFY(statusExport.isOk()); if (!useDocumentExport) { QVERIFY(statusExport == expected); } - if (fail) { - QFAIL(failMessage.toUtf8()); - } + } } #endif