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 index 3e72c541a4..81683d3586 100644 --- 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 @@ -1,33 +1,33 @@ -From 6644f33d9c9be580f3277792e304d20c4b973cdd Mon Sep 17 00:00:00 2001 +From bfa5d9ad432d3be6529d4c0d6fd79aecf8a8bdfa 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 +Subject: [PATCH 01/19] 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-Hack-always-return-we-support-DIBV5.patch b/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch index aa09c1933d..f835c3c00f 100644 --- a/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch +++ b/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch @@ -1,30 +1,30 @@ -From 835bb62519cc53976b8341c0d83a660674f66a92 Mon Sep 17 00:00:00 2001 +From e396f2274a8db88f9bb74a7bec95de76b63f1b33 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Tue, 21 Jun 2016 14:50:47 +0300 -Subject: [PATCH 02/17] Hack: always return we support DIBV5 +Subject: [PATCH 02/19] 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 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.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 index 860d0ae15b..ad5a583fd2 100644 --- 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 @@ -1,1200 +1,1200 @@ -From 721279a37e70459798928babacc2ccc8bf4fca3a Mon Sep 17 00:00:00 2001 +From 9934be5bc72e3fd1c9094ce54df5ae522d6dad4f 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 +Subject: [PATCH 11/19] 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 +index e8f13b388f..e59114bb88 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, +@@ -624,10 +631,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-Implement-color-space-selection-for-QSurfaceFormat.patch b/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch index 303174c533..a3dee87da0 100644 --- a/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch +++ b/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch @@ -1,276 +1,276 @@ -From 806fae9657a13559332796ade9cf1b302d014e46 Mon Sep 17 00:00:00 2001 +From 32171690fab4ae1eb80b50d31f97cde5f1250cc9 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 +Subject: [PATCH 12/19] 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 +index 4e2bcad50f..d8e4c62ddc 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 +index 7d511bf0d7..716e461436 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) +@@ -2875,9 +2875,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 index cd0dbb0bf4..c98df67d0f 100644 --- 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 @@ -1,627 +1,627 @@ -From d9e2e0e62952dfcae6922307555e08ef5bce0c84 Mon Sep 17 00:00:00 2001 +From e3d75d2db98224337c40b4fdc94d443693f57e15 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 +Subject: [PATCH 13/19] 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 +index b709f2f639..8b9142a0ef 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() +@@ -411,6 +543,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() +@@ -458,6 +612,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 +@@ -487,15 +643,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) +@@ -509,6 +676,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) +@@ -517,7 +699,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 +index 142d5ef9bb..e541cb70e4 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h -@@ -655,6 +655,7 @@ public: +@@ -657,6 +657,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 +index 24b8665013..0603137b8e 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 +@@ -1012,7 +1012,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/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch b/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch index 92edcd0c75..7256688546 100644 --- 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 @@ -1,95 +1,95 @@ -From a1131ad9ab08c86eb32ca3f294690a698470bda1 Mon Sep 17 00:00:00 2001 +From ca3018536c7625f76969fe0031e35ead039071ae 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 +Subject: [PATCH 14/19] 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 +index 4137a4bd9a..bc37263298 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) +@@ -328,6 +328,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 index 9d2a59096a..14f0177cc9 100644 --- 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 @@ -1,1368 +1,1368 @@ -From fc02647fc01c20f3bfcc53f38a060328b9e2ab90 Mon Sep 17 00:00:00 2001 +From 610afce97df539c7ceb61f35e15f815e2c1c6996 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 +Subject: [PATCH 15/19] 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 index d51572c13f..62728ef7fe 100644 --- 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 @@ -1,29 +1,29 @@ -From d06dc5de61c3574dd97c5aaae07561d8fc29c604 Mon Sep 17 00:00:00 2001 +From fc3645ac36979772878e9d3380224d870bfb80c5 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 +Subject: [PATCH 16/19] 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/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch b/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch index 644c0cd236..e856669706 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 0ae8de8731d39fd1e736c7d9012f87184299956e Mon Sep 17 00:00:00 2001 +From 79689ebfb428571845dc4deed73ecae3f1fc4781 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Mar 2019 13:18:06 +0300 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 fbc71cd0ea..25cb486915 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -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.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 dd39f1b369..2e684b793d 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 82c789f75fc634ccbe5f1aad6d41895a6e6da17d Mon Sep 17 00:00:00 2001 +From 22c69acd5168dff36197b11edfc3fc0735253b91 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Mar 2019 16:17:17 +0300 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 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.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 e9e0664c1e..23abab7337 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 89b0f14299deac2ad0802f3addfbf487ba680720 Mon Sep 17 00:00:00 2001 +From 0055c47e0f4525ac39565c7ba2eb6a2e62ac6aad Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Mar 2019 13:18:06 +0300 -Subject: [PATCH 09/17] Synthesize Enter/LeaveEvent for accepted QTabletEvent +Subject: [PATCH 03/19] 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 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.20.1.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 3bcbb7e59e..afea29a6d5 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 d95b6e9a4029a46acbe5df4b068de8a646887430 Mon Sep 17 00:00:00 2001 +From 4ba47aed42464e1c39d6f10282f0ebd166abcd1c Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 3 Apr 2019 18:37:56 +0300 -Subject: [PATCH 10/17] Implement a switch for tablet API on Windows +Subject: [PATCH 04/19] 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 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 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.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 2cbec63601..5997b77d4d 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,139 +1,139 @@ -From 8485c169f76c074256616a55308c1a57174ae064 Mon Sep 17 00:00:00 2001 +From 0cfc9a41986397db4b09afed1785d296f8956ce6 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 18:08:33 +0300 -Subject: [PATCH 11/17] Fetch stylus button remapping from WinTab driver +Subject: [PATCH 05/19] 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 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,52 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L return true; } +Qt::MouseButton buttonValueToEnum(DWORD button, + const QWindowsTabletDeviceData &tdd) { + + 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; +} + +Qt::MouseButtons convertTabletButtons(DWORD btnNew, + const QWindowsTabletDeviceData &tdd) { + + 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; + + /** + * 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 +619,12 @@ 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, - 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 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 @@ -100,6 +101,7 @@ struct QWindowsTabletDeviceData qint64 uniqueId = 0; int currentDevice = 0; int currentPointerType = 0; + QHash buttonsMap; }; #ifndef QT_NO_DEBUG_STREAM -- 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 632429d3cb..babe8d7385 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 c5ee9030553cc37d92391b6f60e978eb27b15768 Mon Sep 17 00:00:00 2001 +From 2b7638032c20b8534c27778d0f2ca46ffbb3fedc Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 20:29:14 +0300 -Subject: [PATCH 12/17] Disable tablet relative mode in Qt +Subject: [PATCH 06/19] 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 44b94d044d..6a9fe28e75 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -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.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 1f053612c9..683bfab2ea 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,219 +1,219 @@ -From 71466f90b0b554987a60e5c797d38d6c28f8ebef Mon Sep 17 00:00:00 2001 +From e0556c3526f9f082758a57426d94796992904320 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 23:24:01 +0300 -Subject: [PATCH 13/17] Fetch mapped screen size from the Wintab driver +Subject: [PATCH 07/19] 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 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); @@ -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 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 @@ -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.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 69c2927f63..e47d052464 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,79 +1,79 @@ -From dd25106c340d91d20432375a2a22b85779eddb18 Mon Sep 17 00:00:00 2001 +From 070eb69f2c8812bc62b50196117b6cd2a8d2f3de Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 17 Apr 2019 17:39:10 +0300 -Subject: [PATCH 14/17] Switch stylus pointer type when the tablet is in the +Subject: [PATCH 08/19] 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 | 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 bfd0a9640b..02455536fe 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -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), @@ -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); + 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.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 cda9644e3b..10c12177da 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,40 +1,40 @@ -From f83799913665e06e98c743326eafd47d78087477 Mon Sep 17 00:00:00 2001 +From e4579424942fdaf1f9877d36dda2e6b9f1796b03 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Thu, 18 Apr 2019 15:42:17 +0300 -Subject: [PATCH 15/17] Fix updating tablet pressure resolution on every +Subject: [PATCH 09/19] 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 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.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 deleted file mode 100644 index 64d3476751..0000000000 --- a/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 8281d9ff26581797bf489d937824f2142f97b027 Mon Sep 17 00:00:00 2001 -From: Dmitry Kazakov -Date: Wed, 15 May 2019 19:39:44 +0300 -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 fd3d711470..bf201b87e0 100644 ---- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp -+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -@@ -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, -@@ -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.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 342d4f044e..c366876454 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 c37ff0abfed81284721ec00665579b73107a38ca Mon Sep 17 00:00:00 2001 +From 89a69b6c61df0bd758ffff258b456f21bf7a03cb Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 15 May 2019 19:54:52 +0300 -Subject: [PATCH 3/3] Add workaround for handling table press correctly in +Subject: [PATCH 10/19] 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 a67214bd9a..d431f37d35 100644 +index 5014878bd2..d97ef36ece 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -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 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 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 bf201b87e0..60112f7610 100644 +index 71a09304c5..07a5722de9 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -@@ -541,6 +541,58 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, +@@ -543,6 +543,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) { -@@ -631,10 +683,38 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin +@@ -633,10 +685,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.20.1.windows.1 diff --git a/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch b/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch index 30c0a19ef1..77459c4cc3 100644 --- a/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch +++ b/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch @@ -1,163 +1,163 @@ -From 16a8ff15243a9875fa509f10d1cd9d01df9d4c6d Mon Sep 17 00:00:00 2001 +From 28f0a9f55d129aa33ee2399a4b2dc0a6a9999542 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 21 Nov 2018 09:06:50 +0100 -Subject: [PATCH 16/17] Windows: Add a default setting for +Subject: [PATCH 17/19] 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 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 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 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 7597fcb64d..6949b9c669 100644 +index 716e461436..1d16741d9a 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp -@@ -1256,6 +1256,7 @@ void QWindowCreationContext::applyToMinMaxInfo(MINMAXINFO *mmi) const +@@ -1258,6 +1258,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), -@@ -1293,7 +1294,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) +@@ -1295,7 +1296,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); } -@@ -2956,6 +2957,11 @@ void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border +@@ -2957,6 +2958,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 ce67e46df3..958564aa86 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -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); @@ -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.20.1.windows.1 diff --git a/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch b/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch index 86c5b667e9..d9cd7b77ad 100644 --- a/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch +++ b/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch @@ -1,51 +1,51 @@ -From f14fa33b2c5452be7df163973c26a0d1222696a2 Mon Sep 17 00:00:00 2001 +From 4acf52531fa727a9017a62daf22e239903d2cc83 Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Thu, 18 Apr 2019 18:59:58 +0800 -Subject: [PATCH 17/17] Hack to hide 1px border with OpenGL fullscreen hack +Subject: [PATCH 18/19] 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 6949b9c669..10de52d4d6 100644 +index 1d16741d9a..8bf3aadd4c 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp -@@ -1634,7 +1634,7 @@ void QWindowsWindow::show_sys() const +@@ -1636,7 +1636,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 -@@ -2124,7 +2124,7 @@ bool QWindowsWindow::isFullScreen_sys() const +@@ -2126,7 +2126,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(); } -@@ -2195,7 +2195,11 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState) +@@ -2197,7 +2197,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.20.1.windows.1 diff --git a/3rdparty/ext_qt/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch b/3rdparty/ext_qt/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch index 7d54cc16e6..fb3c54ea3f 100644 --- a/3rdparty/ext_qt/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch +++ b/3rdparty/ext_qt/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch @@ -1,216 +1,216 @@ -From 46e1d5280515002f5694ddf7a9aab0930e2867c1 Mon Sep 17 00:00:00 2001 +From b2f90e81f956fddf4d0115de30ac88a6b2c0bbd4 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Thu, 5 Sep 2019 10:23:08 +0300 -Subject: [PATCH] Fix QRandomGenerator initialization on AMD CPUs +Subject: [PATCH 19/19] Fix QRandomGenerator initialization on AMD CPUs Some AMD CPUs (e.g. AMD A4-6250J and AMD Ryzen 3000-series) have a failing random generation instruction, which always returns 0xffffffff, even when generation was "successful". This code checks if hardware random generator can generate four consecutive distinct numbers. If it fails the test, then we probably have a failing one and should disable it completely. Change-Id: I38c87920ca2e8cce4143afbff5e453ce3845d11a Fixes: QTBUG-69423 --- src/corelib/global/qrandom.cpp | 40 ++-------------------- src/corelib/global/qrandom_p.h | 8 ----- src/corelib/tools/qsimd.cpp | 62 ++++++++++++++++++++++++++++++++++ src/corelib/tools/qsimd_p.h | 18 ++++++++++ 4 files changed, 82 insertions(+), 46 deletions(-) diff --git a/src/corelib/global/qrandom.cpp b/src/corelib/global/qrandom.cpp index bf01b7ae2a..c3ccd2d0cd 100644 --- a/src/corelib/global/qrandom.cpp +++ b/src/corelib/global/qrandom.cpp @@ -90,42 +90,6 @@ DECLSPEC_IMPORT BOOLEAN WINAPI SystemFunction036(PVOID RandomBuffer, ULONG Rando QT_BEGIN_NAMESPACE -#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND) -static qsizetype qt_random_cpu(void *buffer, qsizetype count) Q_DECL_NOTHROW; - -# ifdef Q_PROCESSOR_X86_64 -# define _rdrandXX_step _rdrand64_step -# else -# define _rdrandXX_step _rdrand32_step -# endif - -static QT_FUNCTION_TARGET(RDRND) qsizetype qt_random_cpu(void *buffer, qsizetype count) Q_DECL_NOTHROW -{ - unsigned *ptr = reinterpret_cast(buffer); - unsigned *end = ptr + count; - - while (ptr + sizeof(qregisteruint)/sizeof(*ptr) <= end) { - if (_rdrandXX_step(reinterpret_cast(ptr)) == 0) - goto out; - ptr += sizeof(qregisteruint)/sizeof(*ptr); - } - - if (sizeof(*ptr) != sizeof(qregisteruint) && ptr != end) { - if (_rdrand32_step(ptr)) - goto out; - ++ptr; - } - -out: - return ptr - reinterpret_cast(buffer); -} -#else -static qsizetype qt_random_cpu(void *, qsizetype) -{ - return 0; -} -#endif - enum { // may be "overridden" by a member enum FillBufferNoexcept = true @@ -366,8 +330,8 @@ Q_NEVER_INLINE void QRandomGenerator::SystemGenerator::generate(quint32 *begin, } qsizetype filled = 0; - if (qt_has_hwrng() && (uint(qt_randomdevice_control) & SkipHWRNG) == 0) - filled += qt_random_cpu(buffer, count); + if (qHasHwrng() && (uint(qt_randomdevice_control) & SkipHWRNG) == 0) + filled += qRandomCpu(buffer, count); if (filled != count && (uint(qt_randomdevice_control) & SkipSystemRNG) == 0) { qsizetype bytesFilled = diff --git a/src/corelib/global/qrandom_p.h b/src/corelib/global/qrandom_p.h index 917a91098e..4a0adff51c 100644 --- a/src/corelib/global/qrandom_p.h +++ b/src/corelib/global/qrandom_p.h @@ -79,14 +79,6 @@ extern Q_CORE_EXPORT QBasicAtomicInteger qt_randomdevice_control; enum { qt_randomdevice_control = 0 }; #endif -inline bool qt_has_hwrng() -{ -#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND) - return qCpuHasFeature(RDRND); -#else - return false; -#endif -} QT_END_NAMESPACE diff --git a/src/corelib/tools/qsimd.cpp b/src/corelib/tools/qsimd.cpp -index ddd715f745..3d051df859 100644 +index e44307f28d..898af79b2c 100644 --- a/src/corelib/tools/qsimd.cpp +++ b/src/corelib/tools/qsimd.cpp @@ -376,6 +376,37 @@ static quint64 detectProcessorFeatures() features &= ~AllAVX512; } +#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND) + /** + * Some AMD CPUs (e.g. AMD A4-6250J and AMD Ryzen 3000-series) have a + * failing random generation instruction, which always returns + * 0xffffffff, even when generation was "successful". + * + * This code checks if hardware random generator can generate four + * consecutive distinct numbers. If it fails the test, then we probably + * have a failing one and should disable it completely. + * + * https://bugreports.qt.io/browse/QTBUG-69423 + */ + if (features & CpuFeatureRDRND) { + const qsizetype testBufferSize = 4; + unsigned testBuffer[4] = {}; + + const qsizetype generated = qRandomCpu(testBuffer, testBufferSize); + + if (generated == testBufferSize && + testBuffer[0] == testBuffer[1] && + testBuffer[1] == testBuffer[2] && + testBuffer[2] == testBuffer[3]) { + + fprintf(stderr, "WARNING: CPU random generator seem to be failing, disable hardware random number generation\n"); + fprintf(stderr, "WARNING: RDRND generated: 0x%x 0x%x 0x%x 0x%x\n", testBuffer[0], testBuffer[1], testBuffer[2], testBuffer[3]); + + features &= ~CpuFeatureRDRND; + } + } +#endif + return features; } -@@ -590,4 +621,35 @@ void qDumpCPUFeatures() +@@ -589,4 +620,35 @@ void qDumpCPUFeatures() puts(""); } +#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND) + +# ifdef Q_PROCESSOR_X86_64 +# define _rdrandXX_step _rdrand64_step +# else +# define _rdrandXX_step _rdrand32_step +# endif + +QT_FUNCTION_TARGET(RDRND) qsizetype qRandomCpu(void *buffer, qsizetype count) Q_DECL_NOTHROW +{ + unsigned *ptr = reinterpret_cast(buffer); + unsigned *end = ptr + count; + + while (ptr + sizeof(qregisteruint)/sizeof(*ptr) <= end) { + if (_rdrandXX_step(reinterpret_cast(ptr)) == 0) + goto out; + ptr += sizeof(qregisteruint)/sizeof(*ptr); + } + + if (sizeof(*ptr) != sizeof(qregisteruint) && ptr != end) { + if (_rdrand32_step(ptr)) + goto out; + ++ptr; + } + +out: + return ptr - reinterpret_cast(buffer); +} +#endif + + QT_END_NAMESPACE diff --git a/src/corelib/tools/qsimd_p.h b/src/corelib/tools/qsimd_p.h -index c36e1e484f..d603631a24 100644 +index 9f1321df94..2130a1fbc0 100644 --- a/src/corelib/tools/qsimd_p.h +++ b/src/corelib/tools/qsimd_p.h @@ -346,6 +346,15 @@ extern Q_CORE_EXPORT QBasicAtomicInteger qt_cpu_features[2]; #endif - Q_CORE_EXPORT quint64 qDetectCpuFeatures(); + Q_CORE_EXPORT void qDetectCpuFeatures(); +#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND) +Q_CORE_EXPORT qsizetype qRandomCpu(void *, qsizetype) Q_DECL_NOTHROW; +#else +static inline qsizetype qRandomCpu(void *, qsizetype) Q_DECL_NOTHROW +{ + return 0; +} +#endif + static inline quint64 qCpuFeatures() { quint64 features = qt_cpu_features[0].load(); -@@ -362,6 +371,15 @@ static inline quint64 qCpuFeatures() +@@ -366,6 +375,15 @@ static inline quint64 qCpuFeatures() #define qCpuHasFeature(feature) (((qCompilerCpuFeatures & CpuFeature ## feature) == CpuFeature ## feature) \ || ((qCpuFeatures() & CpuFeature ## feature) == CpuFeature ## feature)) +inline bool qHasHwrng() +{ +#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND) + return qCpuHasFeature(RDRND); +#else + return false; +#endif +} + #define ALIGNMENT_PROLOGUE_16BYTES(ptr, i, length) \ for (; i < static_cast(qMin(static_cast(length), ((4 - ((reinterpret_cast(ptr) >> 2) & 0x3)) & 0x3))); ++i) -- 2.20.1.windows.1 diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 9ba491ea25..f9ae85012c 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,299 +1,297 @@ 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 ${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 ) else() 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}/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 ) # AMD random generation patch set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch ) # Other patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} 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/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.zip - URL_MD5 16526e08adfad46e8e686b8af984b9b5 + URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.zip + URL_MD5 157fa5d9c897737ce06ba4f9a20151e0 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 (ANDROID) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/show-proper-error.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/workaround-null-eglconfig-crash.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/fix-android-menu64bit.patch CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -nomake tests -nomake tools -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -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 -android-sdk ${ANDROID_SDK_ROOT} -android-ndk ${CMAKE_ANDROID_NDK} -android-arch ${ANDROID_ABI} -xplatform android-clang -android-ndk-platform android-21 -make libs INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - 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 + URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz + URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c 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 COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.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 + set(ext_qt_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 COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.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.5/single/qt-everywhere-src-5.12.5.tar.xz + URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c 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/mac_standardpaths_qtbug-61159.diff b/3rdparty/ext_qt/mac_standardpaths_qtbug-61159.diff deleted file mode 100644 index b0a33ed833..0000000000 --- a/3rdparty/ext_qt/mac_standardpaths_qtbug-61159.diff +++ /dev/null @@ -1,59 +0,0 @@ -diff --git a/src/corelib/io/qstandardpaths_mac.mm b/src/corelib/io/qstandardpaths_mac.mm -index e25339a..3bb7a78 100644 ---- a/src/corelib/io/qstandardpaths_mac.mm -+++ b/src/corelib/io/qstandardpaths_mac.mm -@@ -196,42 +196,31 @@ - it != masks.end(); ++it) { - const QString path = baseWritableLocation(type, *it, true); - if (!path.isEmpty() && !dirs.contains(path)) - dirs.append(path); - } - } - - if (type == AppDataLocation || type == AppLocalDataLocation) { - CFBundleRef mainBundle = CFBundleGetMainBundle(); - if (mainBundle) { -- CFURLRef bundleUrl = CFBundleCopyBundleURL(mainBundle); -- CFStringRef cfBundlePath = CFURLCopyFileSystemPath(bundleUrl, kCFURLPOSIXPathStyle); -- QString bundlePath = QString::fromCFString(cfBundlePath); -- CFRelease(cfBundlePath); -- CFRelease(bundleUrl); -- -- CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(mainBundle); -- CFStringRef cfResourcesPath = CFURLCopyFileSystemPath(resourcesUrl, -- kCFURLPOSIXPathStyle); -- QString resourcesPath = QString::fromCFString(cfResourcesPath); -- CFRelease(cfResourcesPath); -- CFRelease(resourcesUrl); -- -- // Handle bundled vs unbundled executables. CFBundleGetMainBundle() returns -- // a valid bundle in both cases. CFBundleCopyResourcesDirectoryURL() returns -- // an absolute path for unbundled executables. -- if (resourcesPath.startsWith(QLatin1Char('/'))) -- dirs.append(resourcesPath); -- else -- dirs.append(bundlePath + resourcesPath); -- } -- } -+ if (QCFType resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle)) { -+ if (QCFType absoluteResouresURL = CFURLCopyAbsoluteURL(resourcesURL)) { -+ if (QCFType path = CFURLCopyFileSystemPath(absoluteResouresURL, -+ kCFURLPOSIXPathStyle)) { -+ dirs.append(QString::fromCFString(path)); -+ } -+ } -+ } -+ } -+ } -+ - const QString localDir = writableLocation(type); - if (!localDir.isEmpty()) - dirs.prepend(localDir); - return dirs; - } - - #ifndef QT_BOOTSTRAPPED - QString QStandardPaths::displayName(StandardLocation type) - { - // Use "Home" instead of the user's Unix username - diff --git a/krita/pics/tools/SVG/16/dark_artistic_text.svg b/krita/pics/tools/SVG/16/dark_artistic_text.svg index 33f384752e..80c45aba7c 100644 --- a/krita/pics/tools/SVG/16/dark_artistic_text.svg +++ b/krita/pics/tools/SVG/16/dark_artistic_text.svg @@ -1,205 +1,69 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + d="m 30.109625,2.1497056 c 0,0 3.036297,11.4038404 -7.176455,21.4821874 -7.774523,7.672201 -21.2304316,5.830883 -21.2304316,5.830883" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#1e1e1e;stroke-width:2.03568077;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:76.15584564px;line-height:50%;font-family:JasmineUPC;-inkscape-font-specification:'JasmineUPC, Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;opacity:0.594;fill:#050505;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 6.9842654,11.950882 c -0.511725,-0.375509 -0.885061,-0.594638 -1.137125,-0.634428 -0.2521,-0.03974 -0.543075,0.15815 -0.872919,0.593677 -0.60634,0.800613 -0.930911,1.482025 -0.973715,2.044237 -0.04436,0.550964 -0.0039,1.176235 0.12137,1.875826 0.123724,0.68833 0.07887,1.173406 -0.134567,1.455225 -0.460817,0.608463 -0.992252,0.684709 -1.594312,0.228742 -0.269005,-0.20373 -0.487258,-0.43455 -0.654782,-0.692429 -1.01687401,-1.092633 -1.52542699,-1.785182 -1.52566199,-2.077641 -6.2e-4,-0.212106 0.21735798,-0.606385 0.65392198,-1.182826 L 10.194394,1.2446616 c 0.436561,-0.57643603 0.754585,-0.88989503 0.954081,-0.94038503 0.464963,-0.121551 1.271835,0.177094 2.420619,0.89593903 0.295188,0.10262 0.574082,0.253369 0.836681,0.452248 0.60206,0.455967 0.675107,0.984978 0.219139,1.587039 -0.213431,0.281815 -0.670621,0.459642 -1.371578,0.533492 -0.707358,0.069 -1.322627,0.202704 -1.845801,0.401116 -0.524723,0.187152 -1.090256,0.681036 -1.696596,1.481649 -0.324996,0.429125 -0.436634,0.76284 -0.334907,1.001137 0.106578,0.23189 0.407125,0.547131 0.918843,0.922649 l 8.915644,6.5425814 c 0.686619,0.503856 1.220389,0.697488 1.624407,0.549941 l 0.364733,-0.16219 c 0.507063,-0.190453 0.895099,-0.183812 1.164101,0.01991 0.550821,0.417161 0.537616,1.006834 -0.03962,1.769014 l -3.456131,4.563482 c -0.596636,0.7878 -1.170369,0.97312 -1.721189,0.555959 -0.198548,-0.150369 -0.307919,-0.298728 -0.328135,-0.445044 -0.02661,-0.151177 -0.02635,-0.377749 7.8e-4,-0.679713 l 0.05,-0.38543 c 0.03254,-0.428884 -0.282948,-0.910737 -0.969564,-1.414597 z" /> diff --git a/krita/pics/tools/SVG/16/dark_calligraphy.svg b/krita/pics/tools/SVG/16/dark_calligraphy.svg index 125a964334..d2cae9bf4b 100644 --- a/krita/pics/tools/SVG/16/dark_calligraphy.svg +++ b/krita/pics/tools/SVG/16/dark_calligraphy.svg @@ -1,178 +1,102 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - image/svg+xml Timothée Giet 2017 + style="opacity:1" + id="layer1"> + d="M 24,-1.6e-6 22,1.9999986 30,9.999999 32,7.9999988 Z" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 22,4 10,5.9999987 8,11.999999 0,26 2,28 12.130859,17.869141 C 12.044806,17.587423 12.000713,17.294567 12,17 c 0,-1.656854 1.343146,-3 3,-3 1.656854,0 3,1.343146 3,3 0,1.656854 -1.343146,3 -3,3 -0.294567,-7.13e-4 -0.587423,-0.04481 -0.869141,-0.130859 L 4,30 l 2,2 14,-8 6,-2 2,-12 z" + style="opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_draw-text.svg b/krita/pics/tools/SVG/16/dark_draw-text.svg index d767c25051..670572fe74 100644 --- a/krita/pics/tools/SVG/16/dark_draw-text.svg +++ b/krita/pics/tools/SVG/16/dark_draw-text.svg @@ -1,199 +1,65 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + id="path4156" /> diff --git a/krita/pics/tools/SVG/16/dark_format-fill-color.svg b/krita/pics/tools/SVG/16/dark_format-fill-color.svg index 6f9b9294e7..1d43577d22 100644 --- a/krita/pics/tools/SVG/16/dark_format-fill-color.svg +++ b/krita/pics/tools/SVG/16/dark_format-fill-color.svg @@ -1,538 +1,352 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> + style="stop-color:#373737;stop-opacity:1;" /> + style="stop-color:#373737;stop-opacity:0;" /> + style="stop-color:#373737;stop-opacity:1;" /> + style="stop-color:#373737;stop-opacity:0;" /> + style="stop-color:#cacaca;stop-opacity:1;" /> + style="stop-color:#cacaca;stop-opacity:0;" /> - - - - - - - - - - - - - - - - - - - - - + x2="6" + y1="16" + x1="26" + id="linearGradient5039" + xlink:href="#linearGradient5033" /> + style="stop-color:#cacaca;stop-opacity:1;" /> + style="stop-color:#cacaca;stop-opacity:0;" /> - + x1="26" + gradientUnits="userSpaceOnUse" + id="linearGradient4406" + xlink:href="#linearGradient5033" /> + + y1="16" + x1="24" + gradientUnits="userSpaceOnUse" + id="linearGradient4225" + xlink:href="#linearGradient4528" /> - + + x2="28" + y1="24" + x1="4" + id="linearGradient4221" + xlink:href="#linearGradient4215" /> - - - image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:none" + id="layer6" /> + id="layer7"> + d="m 4,4 0,8 8,0 0,2 8,0 0,-2 8,0 0,-8 z" + style="opacity:1;fill:url(#linearGradient4213);fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + + y="-5.9999996e-007" + x="30" + height="32" + width="2" + id="rect4258" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + d="m 8,16 14,0 0,0 0,2 0,0 -14,0 0,0 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + + rx="3.3546499e-008" + y="1.9593702e-015" + x="-1.999999" + height="32" + width="2" + id="rect4258-0" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + d="m 22,14 0,6 6,0 0,-6 -6,0 z m 2,2 2,0 0,2 -2,0 0,-2 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + d="m 4,28 0,-6 8,0 0,-2 8,0 0,2 8,0 0,6 z" + style="opacity:1;fill:url(#linearGradient4221);fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_draw_path.svg b/krita/pics/tools/SVG/16/dark_krita_draw_path.svg index 536679b95d..4c2bf57505 100644 --- a/krita/pics/tools/SVG/16/dark_krita_draw_path.svg +++ b/krita/pics/tools/SVG/16/dark_krita_draw_path.svg @@ -1,204 +1,124 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer3"> + cy="28" + cx="16" + id="path4166" + style="opacity:1;fill:none;fill-opacity:1;stroke:#373737;stroke-width:3.99999952;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="display:inline"> + id="path4153" /> + id="path4155" /> + id="path4157" /> + id="path4159" /> + id="path4161" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_assistant.svg b/krita/pics/tools/SVG/16/dark_krita_tool_assistant.svg index 2daf15f778..25d827c18b 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_assistant.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_assistant.svg @@ -1,221 +1,89 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1"> + d="M 2.25,0.015625 32.293928,29.574097 32.4375,32.125 28.6875,32 0.02177185,3.9924175 0.0625,0.015625 Z" + style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 1.8125,3.1875 0,24.0625 24.625,0" + style="fill:none;fill-rule:evenodd;stroke:#323232;stroke-width:3.5999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + height="3.1875" + width="5.875" + id="rect4157" + style="opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - - + + + height="3.8490567" + width="4" + id="rect4157-3-7" + style="display:inline;opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_color_fill.svg b/krita/pics/tools/SVG/16/dark_krita_tool_color_fill.svg index bede4ae178..86c285c312 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_color_fill.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_color_fill.svg @@ -1,183 +1,76 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer1"> + d="M 7.1876411,2.8971925 10.853347,1.0643394 29.181876,21.225722 h -5.498559 z" + style="fill:#323232;fill-opacity:1;stroke:none;stroke-width:0.91642648px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 11.718133,7.6254541 2.553869,16.78972 13.550986,27.786838 22.715251,18.622573" + style="fill:none;stroke:#323232;stroke-width:3.29913521;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 28.33013,21.509226 c 0,0 1.819867,3.21066 2.495505,4.911691 0.675639,1.701029 0.762131,4.333575 -1.671316,4.293075 -2.433446,-0.0405 -2.982218,-2.370466 -2.326766,-3.959532 0.655451,-1.589065 1.502577,-5.245234 1.502577,-5.245234 z" + style="fill:#323232;fill-opacity:1;stroke:none;stroke-width:0.85871017px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 4.3867219,16.78972 H 20.882398 l 1.832853,1.832853 -10.997118,9.164265 -9.164264,-9.164265 1.8328529,-1.832853" + style="fill:#323232;fill-opacity:1;stroke:none;stroke-width:0.91642648px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_color_picker.svg b/krita/pics/tools/SVG/16/dark_krita_tool_color_picker.svg index a251e24955..e37e30cbdc 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_color_picker.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_color_picker.svg @@ -1,181 +1,78 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1"> + x="15.320097" + height="3.0593331" + width="16.764055" + id="rect933" + style="opacity:0.98000004;fill:#323232;fill-opacity:1;stroke:#323232;stroke-width:3.07752299;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + y="-15.974326" + x="20.099665" + height="10.123693" + width="7.3118258" + id="rect933-7" + style="opacity:0.98000004;fill:#323232;fill-opacity:1;stroke:#323232;stroke-width:3.07752299;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 20.597301,16.076505 7.3026933,29.371112 2.3918943,30.000624 3.5042341,25.572653 16.798842,12.278046" + style="fill:none;stroke:#323232;stroke-width:2.67079163;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 7.3026933,21.774195 H 14.899612 L 6.8055089,30.19311 3.5042341,31.270343 v -5.69769 z" + style="fill:#323232;fill-opacity:1;stroke:none;stroke-width:0.8902638px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_dyna.svg b/krita/pics/tools/SVG/16/dark_krita_tool_dyna.svg index 3552b6d26f..44e354daaf 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_dyna.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_dyna.svg @@ -1,156 +1,85 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline;opacity:1"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + cx="17" + cy="11" + r="3" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_ellipse.svg b/krita/pics/tools/SVG/16/dark_krita_tool_ellipse.svg index 733a1f2fdf..8e2e282d14 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_ellipse.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_ellipse.svg @@ -1,159 +1,63 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + cx="16" + id="path4149" + style="opacity:1;fill:none;fill-opacity:1;stroke:#373737;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg b/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg index d46915f888..ba97889713 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg @@ -1,131 +1,70 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - image/svg+xml Timothée Giet 2017 + style="display:inline"> + transform="translate(0,6)" /> + d="m 11.219417,15.892144 c -4.2656666,0.924486 -5.8798289,4.76152 -5.7602025,7.676073 0.1290153,3.143304 -3.2492423,5.634064 -5.37088721,6.064991 2.88480171,2.290554 7.10590391,2.632817 10.91981371,1.858174 5.581946,-1.133748 9.38291,-5.486724 8.3665,-10.490964 -0.763458,-3.75884 -3.973912,-6.014478 -8.155224,-5.108274 z" /> + d="m 15.419679,13.919707 c 2.333842,0.386972 4.895586,2.565916 5.385678,4.577888 0.277189,0.943348 2.614657,-1.018254 2.259914,-2.36818 -1.261052,-2.782065 -3.970326,-4.680627 -6.408155,-4.596194 -0.881229,0.01424 -1.949958,2.340991 -1.237437,2.386486 z" + style="display:inline;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 18.646764,9.7418502 c 2.333842,0.3869718 5.2226,2.7647908 5.712692,4.7767628 0.277189,0.943348 8.629506,-7.8462544 8.274763,-9.1961804 1.08124,-8.2179489 -0.421564,-5.33987312 -7.910756,-5.74524312 -2.383831,0.1468225 -6.78922,10.11916572 -6.076699,10.16466072 z" + style="display:inline;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_freehandvector.svg b/krita/pics/tools/SVG/16/dark_krita_tool_freehandvector.svg index b063dce789..873649b4ba 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_freehandvector.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_freehandvector.svg @@ -1,357 +1,133 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + d="m 18.828125,1.1230469 c -0.443743,-0.00347 -0.883916,0.00567 -1.322266,0.027344 C 16.336928,1.2081891 15.18838,1.3585655 14.056641,1.6210938 10.460813,2.4552133 7.179066,4.5212811 4.3730469,8 L 8,8 8,9.8398438 C 10.132567,7.3770765 12.402119,6.1126443 14.958984,5.5195312 17.293925,4.977898 20.071936,5.1901036 23.046875,5.6660156 20.258356,8.1279927 18.614883,10.996716 18.142578,14.080078 17.642212,17.346631 18.285176,20.676456 19.525391,24 l 4.3125,0 C 22.38426,20.558295 21.683326,17.377677 22.095703,14.685547 22.468882,12.249315 23.667318,10.047766 26.388672,8 L 26,8 24,8 24,6 24,2 24,1.6503906 C 22.238876,1.3454309 20.509708,1.1362105 18.828125,1.1230469 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 24,0 0,2 0,4 0,2 2,0 4,0 2,0 0,-2 0,-6 -8,0 z m 2,2 4,0 0,4 -4,0 0,-4 z" + id="path4234" /> + d="m 18,24 0,2 0,4 0,2 2,0 4,0 2,0 0,-2 0,-6 -8,0 z m 2,2 4,0 0,4 -4,0 0,-4 z" + id="path4236" /> + d="M 0,8 0,9.9999999 0,14 l 0,2 2,0 4,0 2,0 0,-2 0,-6 -8,0 z m 2,1.9999999 4,0 L 6,14 2,14 2,9.9999999 Z" + id="path4238" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_gradient.svg b/krita/pics/tools/SVG/16/dark_krita_tool_gradient.svg index c9803c1dac..12fc2c4d80 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_gradient.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_gradient.svg @@ -1,229 +1,92 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> + style="stop-color:#373737;stop-opacity:1;" /> + style="stop-color:#373737;stop-opacity:0;" /> - - - - - - - - - - - - - - - - - - - - - + x2="28" + y1="16" + x1="4" + id="linearGradient4175" + xlink:href="#linearGradient4169" /> - - - image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + d="m 0,0 0,4 0,24 0,4 32,0 0,-4 0,-24 0,-4 z m 2,2 28,0 0,28 -28,0 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + style="display:inline" + id="layer6"> + d="m 4,3.9999995 24,0 0,1e-7 0,23.9999994 0,1e-6 -24,0 0,-1e-6 0,-23.9999994 z" + style="opacity:1;fill:url(#linearGradient4175);fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + id="layer1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_grid.svg b/krita/pics/tools/SVG/16/dark_krita_tool_grid.svg index 51c8a331d6..05eff3bcf9 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_grid.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_grid.svg @@ -1,236 +1,106 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_lazybrush.svg b/krita/pics/tools/SVG/16/dark_krita_tool_lazybrush.svg index 302cf3accc..05b65782ac 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_lazybrush.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_lazybrush.svg @@ -1,280 +1,128 @@ - - + id="svg2" + viewBox="0 0 16 16" + height="16" + width="16"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Timothée Giet 2016 + style="display:inline"> + transform="translate(0,6)" /> + d="m 8.2182764,21.942684 c -1.5996654,0.346691 -2.2049908,1.785615 -2.1601298,2.8786 0.048385,1.178769 -1.2184969,2.112828 -2.0141339,2.274428 1.0818283,0.85898 2.6647807,0.987332 4.0950332,0.696834 2.0932821,-0.425166 3.5186791,-2.057574 3.1375151,-3.934211 -0.286303,-1.4096 -1.4902528,-2.255486 -3.0582846,-1.915651 z" /> + d="m 9.7934142,21.203002 c 0.8752128,0.145118 1.8358908,0.962243 2.0196798,1.716751 0.103948,0.353764 0.980521,-0.381855 0.847489,-0.88809 -0.472907,-1.0433 -1.48891,-1.755279 -2.403118,-1.723616 -0.3304696,0.0053 -0.7312525,0.877894 -0.4640508,0.894955 z" + style="display:inline;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 11.003601,19.636266 c 0.875213,0.145118 1.958525,1.036823 2.142314,1.791332 0.103948,0.353764 3.236146,-2.94242 3.103114,-3.448655 0.405475,-3.081808 -0.158091,-2.002503 -2.966608,-2.154521 -0.893959,0.05506 -2.546022,3.794783 -2.27882,3.811844 z" + style="display:inline;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 2.5501033,19.843306 2.1213181,2.121318 -0.7071061,0.707107 -1.414212,-2.828425 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 11.035376,28.328579 1.414212,2.828424 0,0 -2.121319,-2.121318 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="M 0.38481876,23.076051 3.2825932,23.852508 3.0237745,24.818432 0.38481876,23.076051 Z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 11.975916,26.181877 2.638956,1.742381 0,0 -2.897775,-0.776457 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 6.0416671,18.126309 0.7764562,2.897774 -0.965925,0.258819 0.1894688,-3.156593 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="M 9.1474924,29.717407 8.9580241,32.874 l 0,0 -0.7764562,-2.897775 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="M 0.126,26.958333 3.0237746,26.181876 3.2825936,27.147801 0.126,26.958333 Z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 1.8429972,30.449896 2.1213183,-2.121317 0.7071063,0.707105 -2.8284246,1.414212 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 5.0757421,32.615181 0.7764567,-2.897774 0.965925,0.258818 -1.7423817,2.638956 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_line.svg b/krita/pics/tools/SVG/16/dark_krita_tool_line.svg index 567a6d15bc..b99dcde1ea 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_line.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_line.svg @@ -1,160 +1,61 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + d="M 2.1171876,29.648438 30.117188,1.648437" + style="fill:none;stroke:#323232;stroke-width:4.03125;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_measure.svg b/krita/pics/tools/SVG/16/dark_krita_tool_measure.svg index 56440ae6bc..949c3f7a20 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_measure.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_measure.svg @@ -1,281 +1,79 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer2"> + d="m 16.078124,18.171891 c 2.793969,3.32545 9,4.731014 13.3125,12.25 l 1.125,0.625 1.5625,-0.875 0,-27.9999999 0,-2.00000036 -4,0 0,2.00000036 0,8.9999999 c -0.903976,-0.47257 -1.8745,-1.153983 -3.678002,-1.285424 -0.762754,-0.05559 -1.53003,0.02616 -2.273438,0.154297 -2.527253,0.435606 -4.671631,1.992169 -5.952857,4.131127 l -0.0957,0 c -1.042589,1.462211 0,4 0,4 z m 8.936773,-5.040134 c 1.402388,0.19339 1.962238,0.731617 2.709143,1.321462 0.104109,0.08222 0.227821,0.145097 0.354084,0.215455 l 0,9.503217 -2,0 c -3.273718,-3.481071 -5.798969,-6.375 -7.351562,-8 0.554615,-1.027171 2.196023,-2.567038 4.036382,-3.010837 0.687772,-0.165855 1.639855,-0.113706 2.251953,-0.0293 z" + style="display:inline;opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:0.50196078" /> + d="m 32.015623,32.171891 c -1.229167,0 -1.173503,0.07024 -2.625,0 L -0.07812507,3.1469234 l 0,-3.10003166 3.96874997,0 L 32.078123,28.234391 Z" + style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_move.svg b/krita/pics/tools/SVG/16/dark_krita_tool_move.svg index ac0119c56a..044468599b 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_move.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_move.svg @@ -1,258 +1,61 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer2"> + d="m 10.724306,6.4221668 5.641035,-5.6721679 5.672168,5.6721679 -3.781446,0 0,18.9072272 3.833872,0 -5.61563,5.699188 -6.200542,-5.699188 4.200855,0 0,-18.9072272 z" + style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 25.529624,9.531961 5.672168,5.641035 -5.672168,5.672168 0,-3.781445 -18.9072278,0 0,3.833871 -5.6991872,-5.615629 5.6991872,-6.2005429 0,4.2008559 18.9072278,0 z" + style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_multihand.svg b/krita/pics/tools/SVG/16/dark_krita_tool_multihand.svg index 2b664edf93..91c3415e78 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_multihand.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_multihand.svg @@ -1,261 +1,128 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + transform="translate(0,6)" /> + id="g4155"> + id="path6" + style="fill:#373737;fill-opacity:1" /> + id="path4174-1" /> + id="path4174-3-9" /> + id="g4155-2" + style="display:inline;fill:#373737;fill-opacity:1"> + id="path6-8" + style="fill:#373737;fill-opacity:1" /> + id="path4174-1-6" /> + id="path4174-3-9-2" /> + id="g4155-2-1" + style="display:inline;fill:#373737;fill-opacity:1"> + id="path6-8-5" + style="fill:#373737;fill-opacity:1" /> + id="path4174-1-6-1" /> + id="path4174-3-9-2-4" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_polygon.svg b/krita/pics/tools/SVG/16/dark_krita_tool_polygon.svg index efd155d3a7..b0486d2ed4 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_polygon.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_polygon.svg @@ -1,270 +1,57 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1"> + d="M 2.2517978,13.928397 14.251798,1.9283968 l 15.831066,0.00321 0.168934,15.9967938 -12,12 -16.0941262,-0.0716 z" + style="fill:none;stroke:#323232;stroke-width:3.09375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_rectangle.svg b/krita/pics/tools/SVG/16/dark_krita_tool_rectangle.svg index 83fa77e0ab..2dee09447f 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_rectangle.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_rectangle.svg @@ -1,191 +1,61 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + d="m 0,0 0,4 0,24 0,4 32,0 0,-4 0,-24 0,-4 z m 4,4 24,0 0,24 -24,0 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_reference_images.svg b/krita/pics/tools/SVG/16/dark_krita_tool_reference_images.svg index 2aa3fe0b92..45e65ec65b 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_reference_images.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_reference_images.svg @@ -1,155 +1,97 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="opacity:1" + id="layer1"> + d="M 20.705881,-5.9999997e-7 11.294116,13.176471 l -7.5294099,0 6.4797789,6.47978 L 0,32 12.343749,21.755516 l 6.47978,6.479779 0,-7.529411 13.176469,-9.411766 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg b/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg index b8c7ff85fd..670512c12b 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg @@ -1,69 +1,39 @@ image/svg+xml2017Timothée Giet \ No newline at end of file + d="m 285.41939,11444.436 161.36493,-161.365 -161.36493,-161.365 -161.36494,161.365 z" + style="fill:#373737;fill-opacity:1;stroke:none;stroke-width:53.788311px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> \ No newline at end of file diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_transform.svg b/krita/pics/tools/SVG/16/dark_krita_tool_transform.svg index 6fc47c6c5b..7c7c6e3e1c 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_transform.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_transform.svg @@ -1,495 +1,357 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + - + y="-1e-006" + x="24" + height="8" + width="8" + id="rect4181-2" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + + + + width="4" + height="4" + x="14" + y="14" + rx="3.3546499e-008" + ry="3.3546499e-008" /> + d="m 0,1.9999994 32,0 0,0 0,4 0,0 -32,0 0,0 0,-4 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + d="m 0,25.999999 32,0 0,0 0,4 0,0 -32,0 0,0 0,-4 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + d="m 2,31.999999 0,-32 0,0 4,0 0,0 0,32 0,0 -4,0 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + d="m 26,31.999999 0,-32 0,0 4,0 0,0 0,32 0,0 -4,0 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> - + x="0" + height="4.0000005" + width="6" + id="rect4235" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + y="14" + x="26" + height="3.9999998" + width="6" + id="rect4237" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + + y="26" + x="14" + height="6" + width="4" + id="rect4241" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_pattern.svg b/krita/pics/tools/SVG/16/dark_pattern.svg index e93b3fb961..bad5088cb6 100644 --- a/krita/pics/tools/SVG/16/dark_pattern.svg +++ b/krita/pics/tools/SVG/16/dark_pattern.svg @@ -1,2447 +1,2152 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + - + - - + + + + - + id="rect4660" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + width="20" + id="rect4662" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + width="20" + id="rect4662-2" + style="display:inline;opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + id="rect4660-8" + style="display:inline;opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + + + y="22" + x="6" + height="4" + width="4" + id="rect4712" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + x="8" + height="4" + width="4" + id="rect4714" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + y="18" + x="10" + height="4" + width="4" + id="rect4716" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/krita/pics/tools/SVG/16/dark_polyline.svg b/krita/pics/tools/SVG/16/dark_polyline.svg index 6eaf141564..a94112a2b7 100644 --- a/krita/pics/tools/SVG/16/dark_polyline.svg +++ b/krita/pics/tools/SVG/16/dark_polyline.svg @@ -1,448 +1,211 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer2"> + d="M 16,2 2,30 30,16 2,2" + style="display:inline;opacity:1;fill:none;fill-rule:evenodd;stroke:#373737;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_select.svg b/krita/pics/tools/SVG/16/dark_select.svg index 0224366d5f..00248260fd 100644 --- a/krita/pics/tools/SVG/16/dark_select.svg +++ b/krita/pics/tools/SVG/16/dark_select.svg @@ -1,160 +1,60 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer4"> + d="m 12.443835,9.3888263 -4.4465,1.5624997 9.8125,21.039062 4.693359,-2.27892 z" + style="opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:0.50196078" /> + d="m 6.1875,28 0.125,-28.0625 20.25,19.5 -12.375,0.25 z" + style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_shape_handling.svg b/krita/pics/tools/SVG/16/dark_shape_handling.svg index d561c4fc3c..e1046d5455 100644 --- a/krita/pics/tools/SVG/16/dark_shape_handling.svg +++ b/krita/pics/tools/SVG/16/dark_shape_handling.svg @@ -1,246 +1,122 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="opacity:1" + id="layer1"> + d="m 14.78887,12.740477 16.760456,5.693053 -8.466644,4.544602 -5.129308,8.972617 z" + style="opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 3.240508,26.935423 c 0,0 -2.471994,-9.60653 5.842702,-18.0964696 C 15.412825,2.375936 26.367933,3.9270517 26.367933,3.9270517" + style="opacity:1;fill:none;fill-rule:evenodd;stroke:#373737;stroke-width:4.13358974;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="3.6685448" + cx="28.072071" + id="path4151" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="27.193954" + cx="3.4839249" + id="path4151-7" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="8.0633898" + cx="10.543903" + id="path4151-3" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_contiguous_selection.svg b/krita/pics/tools/SVG/16/dark_tool_contiguous_selection.svg index 4e6a93bc05..530d7a751b 100644 --- a/krita/pics/tools/SVG/16/dark_tool_contiguous_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_contiguous_selection.svg @@ -1,259 +1,99 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="g4179"> + x="-2.0718551" + height="21.826818" + width="3.6923356" + id="rect4224" + style="opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="11.338273" + cx="10.980621" + id="path4226" + style="opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - + + x="9.2488127" + height="19.138254" + width="3.8761017" + id="rect4232-3" + style="display:inline;opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + x="-1.4681063" + height="15.359143" + width="3.6805413" + id="rect4232-3-6" + style="display:inline;opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + x="13.75814" + height="19.138254" + width="3.8761017" + id="rect4232-7" + style="display:inline;opacity:1;fill:#323232;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_crop.svg b/krita/pics/tools/SVG/16/dark_tool_crop.svg index 4f56e93e58..0b94724943 100644 --- a/krita/pics/tools/SVG/16/dark_tool_crop.svg +++ b/krita/pics/tools/SVG/16/dark_tool_crop.svg @@ -1,237 +1,98 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + id="path4158" /> + id="path4160" /> - + + height="4" + width="4" + id="rect4168" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_elliptical_selection.svg b/krita/pics/tools/SVG/16/dark_tool_elliptical_selection.svg index fe44f12cfa..da2e4e8ade 100644 --- a/krita/pics/tools/SVG/16/dark_tool_elliptical_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_elliptical_selection.svg @@ -1,168 +1,84 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer2"> + d="m 12.380342,3.2697394 c 4.29949,-0.6253802 4.143145,-0.5472077 7.03553,0.078173" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 21.667173,4.7850859 c 3.924451,1.8642431 3.751,1.8425771 5.810892,3.9671545" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 29.276034,12.587905 c 0.634092,4.298213 0.555602,4.142028 -0.06391,7.035673" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 28.470061,22.812124 c -2.455439,3.584344 -2.406896,3.416421 -4.827661,5.118443" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 19.289204,29.285236 c -4.299742,0.623633 -4.143366,0.545525 -7.035496,-0.08103" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 8.5851341,28.144033 C 5.2497564,25.359799 5.4123196,25.424046 3.9474962,22.852806" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 2.8692907,20.135324 C 2.2603328,15.83348 2.3379072,15.990121 2.9743256,13.100146" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 4.6199632,10.065917 C 6.5890692,6.1930213 6.562746,6.3658263 8.7419486,4.3638117" + style="fill:none;stroke:#323232;stroke-width:3.23634243;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_outline_selection.svg b/krita/pics/tools/SVG/16/dark_tool_outline_selection.svg index 3b7d87acdd..1ac6610cb7 100644 --- a/krita/pics/tools/SVG/16/dark_tool_outline_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_outline_selection.svg @@ -1,315 +1,84 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer1"> + d="m 20.565716,5.9193359 c 0,0 3.734992,3.0025093 4.88244,4.7710361" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 27.107149,13.668724 c 0,0 1.255631,4.624782 1.143872,6.729975" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 28.251021,23.832862 c -0.234375,2.226562 -1.542237,5.121412 -4.391659,5.226315" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 19.587987,28.545002 c 0,0 -4.717571,-2.831461 -3.091876,-6.102623" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 12.425757,14.776063 c 1.740146,-0.248592 6.722633,0.788773 4.743188,5.153886" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="M 9.0191753,14.883386 C 5.8551129,16.055261 2.0895549,15.707582 2.4169872,11.181744" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="M 2.4947911,6.662042 C 4.135416,3.4979796 4.9965186,2.8640736 7.9792121,2.5972981" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 11.454005,2.8420561 c 0,0 4.680447,1.028916 6.494524,2.102904" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:3.94968724;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_pan.svg b/krita/pics/tools/SVG/16/dark_tool_pan.svg index 6457c80f88..b7f7da3312 100644 --- a/krita/pics/tools/SVG/16/dark_tool_pan.svg +++ b/krita/pics/tools/SVG/16/dark_tool_pan.svg @@ -1,177 +1,62 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Timothée Giet 2016 + style="display:inline" + id="layer3"> + style="display:inline" /> + d="m 0,14 4,2 2,6 0,-20.0000004 4,-1e-7 L 10,16 12,16 12,-5e-7 l 4,0 L 16,16 l 2,0 0,-14.0000005 4,0 L 22,16 l 2,0 0,-10.0000004 4,0 L 28,32 6,32 2,26 0,14" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_path_selection.svg b/krita/pics/tools/SVG/16/dark_tool_path_selection.svg index 7c8309b30e..de3c030007 100644 --- a/krita/pics/tools/SVG/16/dark_tool_path_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_path_selection.svg @@ -1,229 +1,138 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer2"> - - + + + rx="3.3546499e-008" + y="-4" + x="12" + height="4" + width="8" + id="rect4155-8-7-8" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + style="display:inline"> + id="path4153" /> + id="path4155" /> + id="path4157" /> + id="path4159" /> + id="path4161" /> + d="m 4.2214302,22.661112 c 0.6862653,1.358472 0.864745,3.163245 4.2440098,4.854937" + style="fill:none;fill-rule:evenodd;stroke:#323232;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 23.805725,27.497204 c 1.259109,-0.855015 3.026025,-1.263754 4.269808,-4.832263" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#323232;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_perspectivegrid.svg b/krita/pics/tools/SVG/16/dark_tool_perspectivegrid.svg index 76a4197cd5..0b249328b7 100644 --- a/krita/pics/tools/SVG/16/dark_tool_perspectivegrid.svg +++ b/krita/pics/tools/SVG/16/dark_tool_perspectivegrid.svg @@ -1,165 +1,63 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:none" + id="layer1" /> + style="display:inline" + id="layer2"> + d="M 9,8 8,10 11.554688,10 12,8 9,8 Z m 5.5,0 -0.277344,2 3.554688,0 L 17.5,8 14.5,8 Z M 20,8 20.445312,10 24,10 23,8 20,8 Z M 7,12 5,16 10.222656,16 11.111328,12 7,12 Z m 6.945312,0 -0.55664,4 5.222656,0 -0.55664,-4 -4.109376,0 z m 6.94336,0 0.888672,4 L 27,16 25,12 20.888672,12 Z M 3.5,19 0,26 8,26 9.5546875,19 3.5,19 Z M 12.972656,19 12,26 l 8,0 -0.972656,-7 -6.054688,0 z M 22.445312,19 24,26 l 8,0 -3.5,-7 -6.054688,0 z" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="display:none" + id="layer5" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_polygonal_selection.svg b/krita/pics/tools/SVG/16/dark_tool_polygonal_selection.svg index 3a33cb0707..6baf0b60bb 100644 --- a/krita/pics/tools/SVG/16/dark_tool_polygonal_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_polygonal_selection.svg @@ -1,470 +1,193 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + - + + id="path4541" /> + id="path4543" /> + id="path4555" /> + id="path4541-4" /> + id="path4541-49" /> + id="path4541-49-5" /> + id="path4541-49-4" /> + id="path4541-49-7" /> + id="path4541-4-9" /> + id="path4541-4-1" /> + id="path4541-4-6" /> + id="path4541-4-4" /> + id="path4541-4-44" /> + id="path4541-4-44-6" /> + d="M 5.732857,9.6473496 10.174705,5.100773" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#323232;stroke-width:3.61637855;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 22.075295,26.649227 4.441848,-4.546576" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#323232;stroke-width:3.61637855;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_rect_selection.svg b/krita/pics/tools/SVG/16/dark_tool_rect_selection.svg index 97eeb7ed85..ca35684aff 100644 --- a/krita/pics/tools/SVG/16/dark_tool_rect_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_rect_selection.svg @@ -1,168 +1,65 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer4"> + d="M 0,0 0,4 0,8 4,8 4,4 8,4 8,0 Z m 12,0 0,4 8,0 0,-4 z m 12,0 0,4 4,0 0,4 4,0 0,-4 0,-4 z m -24,12 0,8 4,0 0,-8 z m 28,0 0,8 4,0 0,-8 z m -28,12 0,4 0,4 8,0 0,-4 -4,0 0,-4 z m 28,0 0,4 -4,0 0,4 8,0 0,-4 0,-4 z m -16,4 0,4 8,0 0,-4 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_similar_selection.svg b/krita/pics/tools/SVG/16/dark_tool_similar_selection.svg index 5cd8391290..22c0bb53fd 100644 --- a/krita/pics/tools/SVG/16/dark_tool_similar_selection.svg +++ b/krita/pics/tools/SVG/16/dark_tool_similar_selection.svg @@ -1,189 +1,85 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer4"> + d="M 0,0 0,4 0,8 4,8 4,4 8,4 8,0 Z m 0,12 0,8 4,0 0,-8 z m 0,12 0,4 0,4 8,0 0,-4 -4,0 0,-4 z m 28,0 0,4 -4,0 0,4 8,0 0,-4 0,-4 z m -16,4 0,4 8,0 0,-4 z" + style="opacity:1;fill:#373737;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + id="layer1"> + x="15.603848" + height="4.1542525" + width="13.435029" + id="rect4198" + style="opacity:1;fill:#323232;fill-opacity:1;stroke:#323232;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + y="-18.108068" + x="19.311939" + height="7.9970379" + width="6.0442481" + id="rect4198-3" + style="opacity:1;fill:#323232;fill-opacity:1;stroke:#323232;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 18.473165,8.7322547 8.57367,18.366585 7.689786,23.493109 12.374369,22.520837 22.980971,12.886507" + style="fill:none;fill-rule:evenodd;stroke:#323232;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 7.336233,20.134352 c 0.441942,0 7.071068,0.441942 7.071068,0.441942 l -4.331029,2.828427 -2.828427,-0.265165 z" + style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/dark_tool_zoom.svg b/krita/pics/tools/SVG/16/dark_tool_zoom.svg index 5cd25dee0a..a8eca19796 100644 --- a/krita/pics/tools/SVG/16/dark_tool_zoom.svg +++ b/krita/pics/tools/SVG/16/dark_tool_zoom.svg @@ -1,186 +1,70 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Timothée Giet 2016 + style="display:inline" + id="layer3"> + style="display:inline" /> + d="M 22,2 A 10,10 0 0 0 12,12 10,10 0 0 0 22,22 10,10 0 0 0 32,12 10,10 0 0 0 22,2 Z m 0,4 a 6,6 0 0 1 6,6 6,6 0 0 1 -6,6 6,6 0 0 1 -6,-6 6,6 0 0 1 6,-6 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 18,18 c -4,4 -4,4 -4,4 l -2,-2 4,-4 z" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 12,18 16,22 6,32 2,28 Z" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_artistic_text.svg b/krita/pics/tools/SVG/16/light_artistic_text.svg index 8763e7faad..e8450fcbdb 100644 --- a/krita/pics/tools/SVG/16/light_artistic_text.svg +++ b/krita/pics/tools/SVG/16/light_artistic_text.svg @@ -1,210 +1,74 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + d="m 30.067546,2.2290808 c 0,0 3.036297,11.4038402 -7.176455,21.4821872 -7.774523,7.672201 -21.2304324,5.830883 -21.2304324,5.830883" + style="fill:none;fill-rule:evenodd;stroke:#dcdcdc;stroke-width:2.03568077;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + transform="matrix(0.3409489,-0.45018961,0.45018961,0.3409489,-96.753651,60.714388)"> + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:76.15584564px;line-height:50%;font-family:JasmineUPC;-inkscape-font-specification:'JasmineUPC, Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;opacity:0.594;fill:#f0f0f0;fill-opacity:1" + d="m 179.58309,94.331763 c -0.017,-1.123812 -0.1068,-1.885089 -0.32011,-2.283446 -0.21341,-0.39836 -0.80384,-0.597539 -1.77127,-0.597539 -1.77839,0 -3.08728,0.270315 -3.92667,0.810944 -0.82518,0.526403 -1.66457,1.251984 -2.5182,2.176744 -0.83939,0.910534 -1.57209,1.3658 -2.19809,1.3658 -1.35157,0 -2.02735,-0.668672 -2.02735,-2.006019 0,-0.597538 0.0925,-1.152394 0.27743,-1.664568 0.45526,-2.603558 0.88919,-4.061835 1.30178,-4.37483 0.29875,-0.227634 1.08836,-0.34145 2.3688,-0.34145 l 27.3587,0 c 1.28043,0 2.06291,0.113815 2.34746,0.34145 0.66867,0.526403 1.10971,1.984678 1.32312,4.37483 0.17072,0.526403 0.25608,1.081259 0.25608,1.664568 0,1.337347 -0.66867,2.006019 -2.00602,2.006019 -0.62599,0 -1.36579,-0.455266 -2.21942,-1.3658 -0.85362,-0.92476 -1.70014,-1.650341 -2.53954,-2.176744 -0.82516,-0.540629 -2.12694,-0.810944 -3.90533,-0.810944 -0.95321,0 -1.54364,0.199179 -1.77127,0.597539 -0.2134,0.398357 -0.33709,1.159634 -0.32011,2.283446 l 0.29593,19.580077 c 0.0228,1.50791 0.32011,2.4684 0.96032,2.88098 l 0.61888,0.34147 c 0.81094,0.51217 1.21641,1.06703 1.21641,1.66456 0,1.22353 -0.84651,1.8353 -2.53953,1.8353 l -10.1368,0 c -1.74993,0 -2.6249,-0.61177 -2.6249,-1.8353 0,-0.44103 0.0925,-0.75403 0.27743,-0.93899 0.18496,-0.19918 0.50507,-0.44104 0.96033,-0.72557 l 0.59754,-0.34147 c 0.64021,-0.41258 0.98312,-1.37307 0.96033,-2.88098 z" /> diff --git a/krita/pics/tools/SVG/16/light_calligraphy.svg b/krita/pics/tools/SVG/16/light_calligraphy.svg index cc8db91350..a31b201379 100644 --- a/krita/pics/tools/SVG/16/light_calligraphy.svg +++ b/krita/pics/tools/SVG/16/light_calligraphy.svg @@ -1,178 +1,102 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - image/svg+xml Timothée Giet 2017 + style="opacity:1" + id="layer1"> + d="M 24,-1.6e-6 22,1.9999986 30,9.999999 32,7.9999988 Z" + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 22,4 10,5.9999987 8,11.999999 0,26 2,28 12.130859,17.869141 C 12.044806,17.587423 12.000713,17.294567 12,17 c 0,-1.656854 1.343146,-3 3,-3 1.656854,0 3,1.343146 3,3 0,1.656854 -1.343146,3 -3,3 -0.294567,-7.13e-4 -0.587423,-0.04481 -0.869141,-0.130859 L 4,30 l 2,2 14,-8 6,-2 2,-12 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_draw-text.svg b/krita/pics/tools/SVG/16/light_draw-text.svg index e0fd3c49e9..39ddb09e46 100644 --- a/krita/pics/tools/SVG/16/light_draw-text.svg +++ b/krita/pics/tools/SVG/16/light_draw-text.svg @@ -1,199 +1,65 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + id="path4156" /> diff --git a/krita/pics/tools/SVG/16/light_format-fill-color.svg b/krita/pics/tools/SVG/16/light_format-fill-color.svg index 496a170cf2..dc227d6f48 100644 --- a/krita/pics/tools/SVG/16/light_format-fill-color.svg +++ b/krita/pics/tools/SVG/16/light_format-fill-color.svg @@ -1,526 +1,341 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> + style="stop-color:#cacaca;stop-opacity:1;" /> + style="stop-color:#cacaca;stop-opacity:0;" /> + style="stop-color:#cacaca;stop-opacity:1;" /> + style="stop-color:#cacaca;stop-opacity:0;" /> - - - - - - - - - - - - - - - - - - - - - + x2="6" + y1="16" + x1="26" + id="linearGradient5039" + xlink:href="#linearGradient5033" /> + style="stop-color:#cacaca;stop-opacity:1;" /> + style="stop-color:#cacaca;stop-opacity:0;" /> + - + id="linearGradient4534" + xlink:href="#linearGradient4528" /> + - + id="linearGradient4253" + xlink:href="#linearGradient4247" /> + x2="0" + y1="16" + x1="32" + id="linearGradient4253-5" + xlink:href="#linearGradient4247" /> - - - image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:none" + id="layer6" /> + id="layer7"> + d="m 4,4 0,8 8,0 0,2 8,0 0,-2 8,0 0,-8 z" + style="opacity:1;fill:url(#linearGradient4253);fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + + y="-5.9999996e-007" + x="30" + height="32" + width="2" + id="rect4258" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + d="m 8,16 14,0 0,0 0,2 0,0 -14,0 0,0 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + + rx="3.3546499e-008" + y="1.9593702e-015" + x="-1.999999" + height="32" + width="2" + id="rect4258-0" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + d="m 22,14 0,6 6,0 0,-6 -6,0 z m 2,2 2,0 0,2 -2,0 0,-2 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + d="m 4,28 0,-6 8,0 0,-2 8,0 0,2 8,0 0,6 z" + style="opacity:1;fill:url(#linearGradient4253-5);fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_draw_path.svg b/krita/pics/tools/SVG/16/light_krita_draw_path.svg index acae32d8a0..cd9c8f50ed 100644 --- a/krita/pics/tools/SVG/16/light_krita_draw_path.svg +++ b/krita/pics/tools/SVG/16/light_krita_draw_path.svg @@ -1,204 +1,124 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko + + + + + + + + + + id="layer3"> + cy="28" + cx="16" + id="path4166" + style="opacity:1;fill:none;fill-opacity:1;stroke:#cacaca;stroke-width:3.99999952;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="display:inline"> + id="path4153" /> + id="path4155" /> + id="path4157" /> + id="path4159" /> + id="path4161" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_assistant.svg b/krita/pics/tools/SVG/16/light_krita_tool_assistant.svg index f9009b631a..b0e0425fef 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_assistant.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_assistant.svg @@ -1,221 +1,89 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1"> + d="m 1.84375,0.0078125 30.043928,29.5584715 0.143572,2.550903 -3.75,-0.125 L -0.384478,3.984605 -0.34375,0.0078125 Z" + style="display:inline;fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 1.40625,3.1796875 0,24.0624995 24.625,0" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:3.5999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + height="3.1875" + width="5.875" + id="rect4157" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - - + + + height="3.8490567" + width="4" + id="rect4157-3-7" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_color_fill.svg b/krita/pics/tools/SVG/16/light_krita_tool_color_fill.svg index ae0dd9c8cf..73720aed1a 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_color_fill.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_color_fill.svg @@ -1,185 +1,76 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer1"> + d="M 7.8544525,3.8711242 11.291051,2.1528242 28.474047,21.05412 H 23.319148 Z" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:0.85914981px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 12.101789,8.3038692 3.5102915,16.895368 13.820088,27.205166 22.411586,18.613668" + style="fill:none;stroke:#c8c8c8;stroke-width:3.09293914;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 27.675535,21.319905 c 0,0 1.706125,3.009994 2.339536,4.60471 0.633412,1.594715 0.714498,4.062726 -1.566859,4.024758 -2.281355,-0.03797 -2.795829,-2.222312 -2.181343,-3.712061 0.614486,-1.489749 1.408666,-4.917407 1.408666,-4.917407 z" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:0.80504078px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 5.2285905,16.895368 H 20.693287 l 1.718299,1.7183 -10.309797,8.591498 -8.5914975,-8.591498 1.718299,-1.7183" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:0.85914981px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_color_picker.svg b/krita/pics/tools/SVG/16/light_krita_tool_color_picker.svg index f79a7e3a78..300b75456a 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_color_picker.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_color_picker.svg @@ -1,192 +1,85 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer1"> + x="14.830898" + height="3.0593331" + width="16.764055" + id="rect933" + style="display:inline;opacity:0.98999999;fill:#323232;fill-opacity:1;stroke:#c8c8c8;stroke-width:3.07752299;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + y="-15.337411" + x="19.610466" + height="10.123693" + width="7.3118258" + id="rect933-7" + style="display:inline;opacity:0.98000004;fill:#c8c8c8;fill-opacity:1;stroke:#c8c8c8;stroke-width:3.07752299;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 19.833235,16.204305 6.5386281,29.498912 1.6278291,30.128424 2.7401681,25.700452 16.034776,12.405846" + style="display:inline;fill:none;stroke:#c8c8c8;stroke-width:2.67079163;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 6.5386281,21.901995 H 14.135546 L 6.0414431,30.32091 2.7401681,31.398143 V 25.700452 Z" + style="display:inline;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:0.8902638px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_dyna.svg b/krita/pics/tools/SVG/16/light_krita_tool_dyna.svg index 42b232baf7..ef7f4762dc 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_dyna.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_dyna.svg @@ -1,156 +1,85 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko + + + + + + + + + + style="display:inline;opacity:1"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + cx="17" + cy="11" + r="3" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_ellipse.svg b/krita/pics/tools/SVG/16/light_krita_tool_ellipse.svg index 7abe6c9093..fa1c79f484 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_ellipse.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_ellipse.svg @@ -1,159 +1,63 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + cx="16" + id="path4149" + style="opacity:1;fill:none;fill-opacity:1;stroke:#cacaca;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg b/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg index a6401d5348..c578804071 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg @@ -1,131 +1,70 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - image/svg+xml Timothée Giet 2017 + style="display:inline"> + transform="translate(0,6)" /> + d="m 11.219417,15.892144 c -4.2656666,0.924486 -5.8798289,4.76152 -5.7602025,7.676073 0.1290153,3.143304 -3.2492423,5.634064 -5.37088721,6.064991 2.88480171,2.290554 7.10590391,2.632817 10.91981371,1.858174 5.581946,-1.133748 9.38291,-5.486724 8.3665,-10.490964 -0.763458,-3.75884 -3.973912,-6.014478 -8.155224,-5.108274 z" /> + d="m 15.419679,13.919707 c 2.333842,0.386972 4.895586,2.565916 5.385678,4.577888 0.277189,0.943348 2.614657,-1.018254 2.259914,-2.36818 -1.261052,-2.782065 -3.970326,-4.680627 -6.408155,-4.596194 -0.881229,0.01424 -1.949958,2.340991 -1.237437,2.386486 z" + style="display:inline;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 18.646764,9.7418502 c 2.333842,0.3869718 5.2226,2.7647908 5.712692,4.7767628 0.277189,0.943348 8.629506,-7.8462544 8.274763,-9.1961804 1.08124,-8.2179489 -0.421564,-5.33987312 -7.910756,-5.74524312 -2.383831,0.1468225 -6.78922,10.11916572 -6.076699,10.16466072 z" + style="display:inline;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_freehandvector.svg b/krita/pics/tools/SVG/16/light_krita_tool_freehandvector.svg index 0c972946c1..f4fa316d3e 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_freehandvector.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_freehandvector.svg @@ -1,357 +1,133 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + d="m 18.828125,1.1230469 c -0.443743,-0.00347 -0.883916,0.00567 -1.322266,0.027344 C 16.336928,1.2081891 15.18838,1.3585655 14.056641,1.6210938 10.460813,2.4552133 7.179066,4.5212811 4.3730469,8 L 8,8 8,9.8398438 C 10.132567,7.3770765 12.402119,6.1126443 14.958984,5.5195312 17.293925,4.977898 20.071936,5.1901036 23.046875,5.6660156 20.258356,8.1279927 18.614883,10.996716 18.142578,14.080078 17.642212,17.346631 18.285176,20.676456 19.525391,24 l 4.3125,0 C 22.38426,20.558295 21.683326,17.377677 22.095703,14.685547 22.468882,12.249315 23.667318,10.047766 26.388672,8 L 26,8 24,8 24,6 24,2 24,1.6503906 C 22.238876,1.3454309 20.509708,1.1362105 18.828125,1.1230469 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 24,0 0,2 0,4 0,2 2,0 4,0 2,0 0,-2 0,-6 -8,0 z m 2,2 4,0 0,4 -4,0 0,-4 z" + id="path4234" /> + d="m 18,24 0,2 0,4 0,2 2,0 4,0 2,0 0,-2 0,-6 -8,0 z m 2,2 4,0 0,4 -4,0 0,-4 z" + id="path4236" /> + d="M 0,8 0,9.9999999 0,14 l 0,2 2,0 4,0 2,0 0,-2 0,-6 -8,0 z m 2,1.9999999 4,0 L 6,14 2,14 2,9.9999999 Z" + id="path4238" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_gradient.svg b/krita/pics/tools/SVG/16/light_krita_tool_gradient.svg index 00b0a63757..f52bc8b818 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_gradient.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_gradient.svg @@ -1,229 +1,92 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> + style="stop-color:#cacaca;stop-opacity:1;" /> + style="stop-color:#cacaca;stop-opacity:0;" /> - - - - - - - - - - - - - - - - - - - - - + x2="6" + y1="16" + x1="26" + id="linearGradient5039" + xlink:href="#linearGradient5033" /> - - - image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + d="m 0,0 0,4 0,24 0,4 32,0 0,-4 0,-24 0,-4 z m 2,2 28,0 0,28 -28,0 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + style="display:inline" + id="layer6"> + d="m 4,3.9999995 24,0 0,1e-7 0,23.9999994 0,1e-6 -24,0 0,-1e-6 0,-23.9999994 z" + style="opacity:1;fill:url(#linearGradient5039);fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + id="layer1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_grid.svg b/krita/pics/tools/SVG/16/light_krita_tool_grid.svg index 86938cb26d..d2dcd26e38 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_grid.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_grid.svg @@ -1,236 +1,106 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_lazybrush.svg b/krita/pics/tools/SVG/16/light_krita_tool_lazybrush.svg index a28eaf1c7f..208029079c 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_lazybrush.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_lazybrush.svg @@ -1,280 +1,128 @@ - - + id="svg2" + viewBox="0 0 16 16" + height="16" + width="16"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Timothée Giet 2016 + style="display:inline"> + transform="translate(0,6)" /> + d="m 8.2182764,21.942684 c -1.5996654,0.346691 -2.2049908,1.785615 -2.1601298,2.8786 0.048385,1.178769 -1.2184969,2.112828 -2.0141339,2.274428 1.0818283,0.85898 2.6647807,0.987332 4.0950332,0.696834 2.0932821,-0.425166 3.5186791,-2.057574 3.1375151,-3.934211 -0.286303,-1.4096 -1.4902528,-2.255486 -3.0582846,-1.915651 z" /> + d="m 9.7934142,21.203002 c 0.8752128,0.145118 1.8358908,0.962243 2.0196798,1.716751 0.103948,0.353764 0.980521,-0.381855 0.847489,-0.88809 -0.472907,-1.0433 -1.48891,-1.755279 -2.403118,-1.723616 -0.3304696,0.0053 -0.7312525,0.877894 -0.4640508,0.894955 z" + style="display:inline;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 11.003601,19.636266 c 0.875213,0.145118 1.958525,1.036823 2.142314,1.791332 0.103948,0.353764 3.236146,-2.94242 3.103114,-3.448655 0.405475,-3.081808 -0.158091,-2.002503 -2.966608,-2.154521 -0.893959,0.05506 -2.546022,3.794783 -2.27882,3.811844 z" + style="display:inline;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 2.5501033,19.843306 2.1213181,2.121318 -0.7071061,0.707107 -1.414212,-2.828425 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 11.035376,28.328579 1.414212,2.828424 0,0 -2.121319,-2.121318 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="M 0.38481876,23.076051 3.2825932,23.852508 3.0237745,24.818432 0.38481876,23.076051 Z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 11.975916,26.181877 2.638956,1.742381 0,0 -2.897775,-0.776457 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 6.0416671,18.126309 0.7764562,2.897774 -0.965925,0.258819 0.1894688,-3.156593 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="M 9.1474924,29.717407 8.9580241,32.874 l 0,0 -0.7764562,-2.897775 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="M 0.126,26.958333 3.0237746,26.181876 3.2825936,27.147801 0.126,26.958333 Z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 1.8429972,30.449896 2.1213183,-2.121317 0.7071063,0.707105 -2.8284246,1.414212 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 5.0757421,32.615181 0.7764567,-2.897774 0.965925,0.258818 -1.7423817,2.638956 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_line.svg b/krita/pics/tools/SVG/16/light_krita_tool_line.svg index 1e32f6bd5c..7288e33a7f 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_line.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_line.svg @@ -1,165 +1,66 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + id="g862"> + id="path843" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_measure.svg b/krita/pics/tools/SVG/16/light_krita_tool_measure.svg index 6e52d34eaa..18780b4d99 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_measure.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_measure.svg @@ -1,281 +1,79 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer2"> + d="m 16,18 c 2.793969,3.32545 9,4.731014 13.3125,12.25 l 1.125,0.625 L 32,30 32,2 l 0,-2 -4,0 0,2 0,9 C 27.096024,10.52743 26.1255,9.8460173 24.321998,9.714576 23.559244,9.6589856 22.791968,9.7407366 22.04856,9.8688731 19.521307,10.304479 17.376929,11.861042 16.095703,14 L 16,14 c -1.042589,1.462211 0,4 0,4 z m 8.936773,-5.040134 c 1.402388,0.19339 1.962238,0.731617 2.709143,1.321462 0.104109,0.08222 0.227821,0.145097 0.354084,0.215455 L 28,24 26,24 c -3.273718,-3.481071 -5.798969,-6.375 -7.351562,-8 0.554615,-1.027171 2.196023,-2.567038 4.036382,-3.010837 0.687772,-0.165855 1.639855,-0.113706 2.251953,-0.0293 z" + style="display:inline;opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:0.50196078" /> + d="m 31.937499,32 c -1.229167,0 -1.173503,0.07024 -2.625,0 L -0.15625009,2.9750323 l 0,-3.10003145 3.96874999,0 L 31.999999,28.0625 Z" + style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_move.svg b/krita/pics/tools/SVG/16/light_krita_tool_move.svg index f7655ad09a..a2d47f3f8b 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_move.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_move.svg @@ -1,262 +1,63 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1" /> + id="layer2"> + d="m 10.865023,7.4750969 5.415419,-5.4453063 5.445306,5.4453063 -3.630204,0 0,18.1510211 3.680533,0 -5.391029,5.471246 -5.952548,-5.471246 4.03284,0 0,-18.1510211 z" + style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 24.909159,10.700371 5.445306,5.415418 -5.445306,5.445307 0,-3.630205 -18.1510213,0 0,3.680534 -5.4712458,-5.391029 5.4712458,-5.952549 0,4.03284 18.1510213,0 z" + style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_multihand.svg b/krita/pics/tools/SVG/16/light_krita_tool_multihand.svg index 4bcc1234c1..d97a3d3124 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_multihand.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_multihand.svg @@ -1,260 +1,127 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko + + + + + + + + + + style="display:inline"> + transform="translate(0,6)" /> + transform="matrix(0.55,0,0,0.55,5.4330534,9.035957)" + id="g4155"> + id="path6" + style="fill:#cacaca" /> + id="path4174-1" /> + id="path4174-3-9" /> + id="g4155-2" + style="display:inline"> + id="path6-8" + style="fill:#cacaca" /> + id="path4174-1-6" /> + id="path4174-3-9-2" /> + id="g4155-2-1" + style="display:inline"> + id="path6-8-5" + style="fill:#cacaca" /> + id="path4174-1-6-1" /> + id="path4174-3-9-2-4" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_polygon.svg b/krita/pics/tools/SVG/16/light_krita_tool_polygon.svg index 455c82673a..1e1392ecc6 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_polygon.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_polygon.svg @@ -1,270 +1,57 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="g4519"> + d="M 2.2201103,14.061138 14.22011,2.0611374 l 15.831066,0.00321 0.168935,15.9967946 -12.000001,12 -16.0941257,-0.0716 z" + style="display:inline;fill:none;stroke:#c8c8c8;stroke-width:3.09375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_rectangle.svg b/krita/pics/tools/SVG/16/light_krita_tool_rectangle.svg index bc5db508f0..bfb133e02c 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_rectangle.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_rectangle.svg @@ -1,191 +1,61 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer3"> + style="display:inline" /> + d="m 0,0 0,4 0,24 0,4 32,0 0,-4 0,-24 0,-4 z m 4,4 24,0 0,24 -24,0 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_reference_images.svg b/krita/pics/tools/SVG/16/light_krita_tool_reference_images.svg index 42f486435d..5e3d33ddec 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_reference_images.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_reference_images.svg @@ -1,155 +1,97 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="opacity:1" + id="layer1"> + d="M 20.705882,-5.9999998e-7 11.294116,13.17647 l -7.5294103,0 6.4797803,6.47978 L 0,32 12.34375,21.755516 l 6.47978,6.479779 0,-7.529411 13.176469,-9.411766 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> diff --git a/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg b/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg index 9a219d8770..2373fb73bb 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg @@ -1,69 +1,39 @@ image/svg+xml2017Timothée Giet \ No newline at end of file + d="m 285.41939,11444.436 161.36493,-161.365 -161.36493,-161.365 -161.36494,161.365 z" + style="fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:53.788311px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> \ No newline at end of file diff --git a/krita/pics/tools/SVG/16/light_krita_tool_transform.svg b/krita/pics/tools/SVG/16/light_krita_tool_transform.svg index 2be8cf821e..67db228636 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_transform.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_transform.svg @@ -1,495 +1,357 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + - + y="-1e-006" + x="24" + height="8" + width="8" + id="rect4181-2" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> - + + + + d="m 0,1.9999994 32,0 0,0 0,4 0,0 -32,0 0,0 0,-4 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + d="m 0,25.999999 32,0 0,0 0,4 0,0 -32,0 0,0 0,-4 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + d="m 2,31.999999 0,-32 0,0 4,0 0,0 0,32 0,0 -4,0 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + d="m 26,31.999999 0,-32 0,0 4,0 0,0 0,32 0,0 -4,0 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> - + x="0" + height="4.0000005" + width="6" + id="rect4235" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + y="14" + x="26" + height="3.9999998" + width="6" + id="rect4237" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + + y="26" + x="14" + height="6" + width="4" + id="rect4241" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_pattern.svg b/krita/pics/tools/SVG/16/light_pattern.svg index 553b11535d..59617973a1 100644 --- a/krita/pics/tools/SVG/16/light_pattern.svg +++ b/krita/pics/tools/SVG/16/light_pattern.svg @@ -1,2447 +1,2152 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + - + - - + + + + - + id="rect4660" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + width="20" + id="rect4662" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + width="20" + id="rect4662-2" + style="display:inline;opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + id="rect4660-8" + style="display:inline;opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + + + y="22" + x="6" + height="4" + width="4" + id="rect4712" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - + x="8" + height="4" + width="4" + id="rect4714" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + y="18" + x="10" + height="4" + width="4" + id="rect4716" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + y="16" + x="12" + height="4" + width="4" + id="rect4718" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + y="14" + x="14" + height="4" + width="4" + id="rect4720" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/krita/pics/tools/SVG/16/light_polyline.svg b/krita/pics/tools/SVG/16/light_polyline.svg index 5358310904..e2ecc27ba3 100644 --- a/krita/pics/tools/SVG/16/light_polyline.svg +++ b/krita/pics/tools/SVG/16/light_polyline.svg @@ -1,448 +1,211 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer2"> + d="M 16,2 2,30 30,16 2,2" + style="display:inline;opacity:1;fill:none;fill-rule:evenodd;stroke:#cacaca;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_select.svg b/krita/pics/tools/SVG/16/light_select.svg index 87d7a4e810..6618ffa7b3 100644 --- a/krita/pics/tools/SVG/16/light_select.svg +++ b/krita/pics/tools/SVG/16/light_select.svg @@ -1,160 +1,61 @@ - - + id="svg2" + viewBox="0 0 20.375 31.777001" + height="31.777" + width="20.375"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + transform="translate(-6.125,-0.1249999)" + id="layer4"> + d="M 12.459,9.300438 8.0125,10.862938 17.825,31.902 22.518359,29.62308 Z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:0.50196078" /> + d="M 6.125,28.1875 6.25,0.1249999 26.5,19.625 14.125,19.875 Z" + style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_shape_handling.svg b/krita/pics/tools/SVG/16/light_shape_handling.svg index ed36407a7c..7d8f978171 100644 --- a/krita/pics/tools/SVG/16/light_shape_handling.svg +++ b/krita/pics/tools/SVG/16/light_shape_handling.svg @@ -1,246 +1,122 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="opacity:1" + id="layer1"> + d="m 15.060848,12.783895 16.760457,5.693054 -8.466644,4.544601 -5.129308,8.972618 z" + style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 3.5124865,26.978842 c 0,0 -2.4719942,-9.606531 5.8427018,-18.0964696 C 15.684803,2.419355 26.639911,3.9704707 26.639911,3.9704707" + style="fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:4.13358974;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="3.7119639" + cx="28.344049" + id="path4151" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="27.237371" + cx="3.7559054" + id="path4151-7" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="8.1068087" + cx="10.815882" + id="path4151-3" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_contiguous_selection.svg b/krita/pics/tools/SVG/16/light_tool_contiguous_selection.svg index de34494843..8d64d4c264 100644 --- a/krita/pics/tools/SVG/16/light_tool_contiguous_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_contiguous_selection.svg @@ -1,259 +1,99 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1"> + x="-2.2211587" + height="21.826818" + width="3.6923356" + id="rect4224" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + cy="11.160866" + cx="10.58817" + id="path4226" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - + + x="9.0714035" + height="19.138254" + width="3.8761017" + id="rect4232-3" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + x="-1.3318559" + height="15.359143" + width="3.6805413" + id="rect4232-3-6" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + x="13.355186" + height="19.138254" + width="3.8761017" + id="rect4232-7" + style="display:inline;opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:3.5999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_crop.svg b/krita/pics/tools/SVG/16/light_tool_crop.svg index 289d637972..885bd200b5 100644 --- a/krita/pics/tools/SVG/16/light_tool_crop.svg +++ b/krita/pics/tools/SVG/16/light_tool_crop.svg @@ -1,237 +1,98 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline"> + id="path4158" /> + id="path4160" /> - + + height="4" + width="4" + id="rect4168" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_elliptical_selection.svg b/krita/pics/tools/SVG/16/light_tool_elliptical_selection.svg index d877e3a2b5..be18d6557e 100644 --- a/krita/pics/tools/SVG/16/light_tool_elliptical_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_elliptical_selection.svg @@ -1,168 +1,84 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="34.133335" + width="34.133335"> - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + id="layer2"> + d="m 12.51554,2.9161979 c 4.29949,-0.625381 4.143145,-0.547208 7.035529,0.07817" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 21.80237,4.4315439 c 3.924451,1.8642432 3.751001,1.8425772 5.810892,3.9671545" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 29.411232,12.234364 c 0.634092,4.298213 0.555602,4.142027 -0.06391,7.035672" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 28.605258,22.458583 c -2.455439,3.584343 -2.406896,3.41642 -4.82766,5.118442" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 19.424402,28.931695 c -4.299743,0.623632 -4.143367,0.545524 -7.035497,-0.08103" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 8.7203315,27.790492 C 5.3849535,25.006257 5.5475175,25.070504 4.0826935,22.499264" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 3.0044885,19.781782 c -0.608958,-4.301843 -0.531384,-4.145203 0.105035,-7.035178" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 4.7551605,9.712375 C 6.7242665,5.8394793 6.6979435,6.0122844 8.8771465,4.0102698" + style="opacity:0.97000002;fill:none;stroke:#c8c8c8;stroke-width:3.23624992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_outline_selection.svg b/krita/pics/tools/SVG/16/light_tool_outline_selection.svg index 30adda47b9..0377b1d81e 100644 --- a/krita/pics/tools/SVG/16/light_tool_outline_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_outline_selection.svg @@ -1,366 +1,117 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer1"> + d="m 21.410973,5.8367911 c 0,0 3.87067,3.1115782 5.059799,4.9443489" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 28.19003,13.867683 c 0,0 1.301243,4.792782 1.185424,6.974448" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 29.375454,24.401043 c -0.242889,2.307444 -1.59826,5.307452 -4.551189,5.416165" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 20.397727,29.284356 c 0,0 -4.888941,-2.934317 -3.204191,-6.324307" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 12.975323,15.015247 c 1.803359,-0.257622 6.966839,0.817426 4.915489,5.341106" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="M 9.444994,15.126469 C 6.1659941,16.340913 2.2636481,15.980605 2.6029745,11.290361" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="M 2.6836049,6.6064767 C 4.3838271,3.3274768 5.2762105,2.6705436 8.3672523,2.3940772" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + d="m 11.968271,2.6477263 c 0,0 4.850468,1.0662923 6.730444,2.1792939" + style="display:inline;opacity:0.98000004;fill:none;fill-opacity:1;stroke:#323232;stroke-width:4.09316301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.4000001;stroke-dasharray:none;stroke-opacity:1" /> + id="path4196" /> + id="path4198" /> + id="path4200" /> + id="path4202" /> + id="path4204" /> + id="path4206" /> + id="path4208" /> + id="path4210" /> diff --git a/krita/pics/tools/SVG/16/light_tool_pan.svg b/krita/pics/tools/SVG/16/light_tool_pan.svg index 3efecf5e0b..c60e3c9f81 100644 --- a/krita/pics/tools/SVG/16/light_tool_pan.svg +++ b/krita/pics/tools/SVG/16/light_tool_pan.svg @@ -1,177 +1,62 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Timothée Giet 2016 + style="display:inline" + id="layer3"> + style="display:inline" /> + d="m 0,14 4,2 2,6 0,-20.0000004 4,-1e-7 L 10,16 12,16 12,-5e-7 l 4,0 L 16,16 l 2,0 0,-14.0000005 4,0 L 22,16 l 2,0 0,-10.0000004 4,0 L 28,32 6,32 2,26 0,14" + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_path_selection.svg b/krita/pics/tools/SVG/16/light_tool_path_selection.svg index de10c97e2b..1b7239765e 100644 --- a/krita/pics/tools/SVG/16/light_tool_path_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_path_selection.svg @@ -1,192 +1,114 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer2"> - - + + + rx="3.3546499e-08" + y="-4" + x="12" + height="4" + width="8" + id="rect4155-8-7-8" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + style="display:inline"> + id="path4153" /> + id="path4155" /> + id="path4157" /> + id="path4159" /> + id="path4161" /> + d="m 4.5369275,22.742829 c 0.686265,1.358472 0.864745,3.163245 4.244009,4.854937" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 24.121221,27.578921 c 1.259109,-0.855015 3.026025,-1.263754 4.269808,-4.832263" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_perspectivegrid.svg b/krita/pics/tools/SVG/16/light_tool_perspectivegrid.svg index d86b5ee6fe..665f42725c 100644 --- a/krita/pics/tools/SVG/16/light_tool_perspectivegrid.svg +++ b/krita/pics/tools/SVG/16/light_tool_perspectivegrid.svg @@ -1,165 +1,63 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko + + + + + + + + + + style="display:none" + id="layer1" /> + style="display:inline" + id="layer2"> + d="M 9,8 8,10 11.554688,10 12,8 9,8 Z m 5.5,0 -0.277344,2 3.554688,0 L 17.5,8 14.5,8 Z M 20,8 20.445312,10 24,10 23,8 20,8 Z M 7,12 5,16 10.222656,16 11.111328,12 7,12 Z m 6.945312,0 -0.55664,4 5.222656,0 -0.55664,-4 -4.109376,0 z m 6.94336,0 0.888672,4 L 27,16 25,12 20.888672,12 Z M 3.5,19 0,26 8,26 9.5546875,19 3.5,19 Z M 12.972656,19 12,26 l 8,0 -0.972656,-7 -6.054688,0 z M 22.445312,19 24,26 l 8,0 -3.5,-7 -6.054688,0 z" + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="display:none" + id="layer5" /> diff --git a/krita/pics/tools/SVG/16/light_tool_polygonal_selection.svg b/krita/pics/tools/SVG/16/light_tool_polygonal_selection.svg index 4ee59f5de2..da3830d59a 100644 --- a/krita/pics/tools/SVG/16/light_tool_polygonal_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_polygonal_selection.svg @@ -1,470 +1,193 @@ - - + viewBox="0 0 32 32.000001" + id="svg2" + version="1.1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + - + + id="path4541" /> + id="path4543" /> + id="path4555" /> + id="path4541-4" /> + id="path4541-49" /> + id="path4541-49-5" /> + id="path4541-49-4" /> + id="path4541-49-7" /> + id="path4541-4-9" /> + id="path4541-4-1" /> + id="path4541-4-6" /> + id="path4541-4-4" /> + id="path4541-4-44" /> + id="path4541-4-44-6" /> + d="M 5.686638,9.45891 10.128486,4.9123332" + style="fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:3.61637855;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 22.029076,26.460788 4.441848,-4.546576" + style="display:inline;fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:3.61637855;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_rect_selection.svg b/krita/pics/tools/SVG/16/light_tool_rect_selection.svg index 1b9d218146..30c0214b15 100644 --- a/krita/pics/tools/SVG/16/light_tool_rect_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_rect_selection.svg @@ -1,168 +1,65 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer4"> + d="M 0,0 0,4 0,8 4,8 4,4 8,4 8,0 Z m 12,0 0,4 8,0 0,-4 z m 12,0 0,4 4,0 0,4 4,0 0,-4 0,-4 z m -24,12 0,8 4,0 0,-8 z m 28,0 0,8 4,0 0,-8 z m -28,12 0,4 0,4 8,0 0,-4 -4,0 0,-4 z m 28,0 0,4 -4,0 0,4 8,0 0,-4 0,-4 z m -16,4 0,4 8,0 0,-4 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_similar_selection.svg b/krita/pics/tools/SVG/16/light_tool_similar_selection.svg index 8442adc62b..f53475879a 100644 --- a/krita/pics/tools/SVG/16/light_tool_similar_selection.svg +++ b/krita/pics/tools/SVG/16/light_tool_similar_selection.svg @@ -1,189 +1,85 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko + + + + + + + + + + style="display:inline" + id="layer4"> + d="M 0,0 0,4 0,8 4,8 4,4 8,4 8,0 Z m 0,12 0,8 4,0 0,-8 z m 0,12 0,4 0,4 8,0 0,-4 -4,0 0,-4 z m 28,0 0,4 -4,0 0,4 8,0 0,-4 0,-4 z m -16,4 0,4 8,0 0,-4 z" + style="opacity:1;fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1" /> + id="layer1"> + x="15.10516" + height="4.1542525" + width="13.435029" + id="rect4198" + style="opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:#c8c8c8;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + y="-18.578403" + x="18.813251" + height="7.9970379" + width="6.0442481" + id="rect4198-3" + style="opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:#c8c8c8;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="M 18.473165,8.0467578 8.5736699,17.681088 7.6897859,22.807612 12.374369,21.83534 22.980971,12.20101" + style="fill:none;fill-rule:evenodd;stroke:#c8c8c8;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m 7.3362329,19.448855 c 0.441942,0 7.0710681,0.441942 7.0710681,0.441942 l -4.331029,2.828427 -2.8284271,-0.265165 z" + style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/krita/pics/tools/SVG/16/light_tool_zoom.svg b/krita/pics/tools/SVG/16/light_tool_zoom.svg index 8b03b0a387..7279e49d92 100644 --- a/krita/pics/tools/SVG/16/light_tool_zoom.svg +++ b/krita/pics/tools/SVG/16/light_tool_zoom.svg @@ -1,186 +1,70 @@ - - + id="svg2" + viewBox="0 0 32 32.000001" + height="32" + width="32"> - - - - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Timothée Giet 2016 + style="display:inline" + id="layer3"> + style="display:inline" /> + d="M 22,2 A 10,10 0 0 0 12,12 10,10 0 0 0 22,22 10,10 0 0 0 32,12 10,10 0 0 0 22,2 Z m 0,4 a 6,6 0 0 1 6,6 6,6 0 0 1 -6,6 6,6 0 0 1 -6,-6 6,6 0 0 1 6,-6 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 18,18 c -4,4 -4,4 -4,4 l -2,-2 4,-4 z" + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="M 12,18 16,22 6,32 2,28 Z" + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> diff --git a/libs/brush/kis_pipebrush_parasite.h b/libs/brush/kis_pipebrush_parasite.h index 14fd3db54c..c242467788 100644 --- a/libs/brush/kis_pipebrush_parasite.h +++ b/libs/brush/kis_pipebrush_parasite.h @@ -1,120 +1,120 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Bart Coppens * * 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_PIPE_BRUSH_P_H #define KIS_IMAGE_PIPE_BRUSH_P_H #include "kis_imagepipe_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_paint_device.h" #include "kis_layer.h" #include "kritabrush_export.h" /** * The parasite info that gets loaded from the terribly documented gimp pipe brush parasite. * * We only store data we actually use. * * BC: How it seems the dimension stuff interacts with rank, selectionMode and the actual * selection of a brush to be drawn. So apparently you can have at most 4 'dimensions'. * Each dimension has a number of brushes, the rank. Each dimension has an associated selection * mode and placement mode (which we don't use). The selection mode says us in which way * which of the brushes or brush sets will be selected. In the case of a 1-dimensional pipe * brush it is easy. * * However, when there are more dimensions it is a bit harder. You can according to the gimp * source maximally use 4 dimensions. When you want to select a brush, you first go to the * first dimension. Say it has a rank of 2. The code chooses one of the 2 according to the * selection mode. Say we choose 2. Then the currentBrush will skip over all the brushes * from the first element in dimension 1. Then in dimension we pick again from the choices * we have in dimension 2. We again add the appropriate amount to currentBrush. And so on, * until we have reached dimension dim. Or at least, that is how it looks like, we'll know * for sure when we can test it better with >1 dim brushes and Angular selectionMode. **/ class BRUSH_EXPORT KisPipeBrushParasite { public: /// Set some default values KisPipeBrushParasite() : ncells(0) , dim(0) , needsMovement(false) { init(); } void init(); void sanitize(); /// Initializes the brushesCount helper void setBrushesCount(); /// Load the parasite from the source string KisPipeBrushParasite(const QString& source); /** * Saves a GIMP-compatible representation of this parasite to the device. Also writes the * number of brushes (== ncells) (no trailing '\n') */ bool saveToDevice(QIODevice* dev) const; bool loadFromDevice(QIODevice *dev); enum Placement { DefaultPlacement, ConstantPlacement, RandomPlacement }; static int const MaxDim = 4; //qint32 step; - qint32 ncells; - qint32 dim; + qint32 ncells {0}; + qint32 dim {0}; // Apparently only used for editing a pipe brush, which we won't at the moment // qint32 cols, rows; // qint32 cellwidth, cellheight; // Apparently the gimp doesn't use this anymore? Anyway it is a bit weird to // paint at someplace else than where your cursor displays it will... //Placement placement; - qint32 rank[MaxDim]; + qint32 rank[MaxDim] {}; KisParasite::SelectionMode selection[MaxDim]; QString selectionMode; // for UI /// The total count of brushes in each dimension (helper) qint32 brushesCount[MaxDim]; /// The current index in each dimension, so that the selection modes know where to start qint32 index[MaxDim]; /// If true, the brush won't be painted when there is no motion - bool needsMovement; + bool needsMovement {false}; }; #endif diff --git a/libs/flake/KoPathSegment.cpp b/libs/flake/KoPathSegment.cpp index 88979faebe..240676cf70 100644 --- a/libs/flake/KoPathSegment.cpp +++ b/libs/flake/KoPathSegment.cpp @@ -1,1479 +1,1481 @@ /* This file is part of the KDE project * Copyright (C) 2008-2009 Jan Hambrecht * * 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 "KoPathSegment.h" #include "KoPathPoint.h" #include #include #include #include #include "kis_global.h" /// Maximal recursion depth for finding root params const int MaxRecursionDepth = 64; /// Flatness tolerance for finding root params const qreal FlatnessTolerance = ldexp(1.0,-MaxRecursionDepth-1); class BezierSegment { public: BezierSegment(int degree = 0, QPointF *p = 0) { if (degree) { for (int i = 0; i <= degree; ++i) points.append(p[i]); } } void setDegree(int degree) { points.clear(); if (degree) { for (int i = 0; i <= degree; ++i) points.append(QPointF()); } } int degree() const { return points.count() - 1; } QPointF point(int index) const { if (static_cast(index) > degree()) return QPointF(); return points[index]; } void setPoint(int index, const QPointF &p) { if (static_cast(index) > degree()) return; points[index] = p; } QPointF evaluate(qreal t, BezierSegment *left, BezierSegment *right) const { int deg = degree(); if (! deg) return QPointF(); QVector > Vtemp(deg + 1); for (int i = 0; i <= deg; ++i) Vtemp[i].resize(deg + 1); /* Copy control points */ for (int j = 0; j <= deg; j++) { Vtemp[0][j] = points[j]; } /* Triangle computation */ for (int i = 1; i <= deg; i++) { for (int j =0 ; j <= deg - i; j++) { Vtemp[i][j].rx() = (1.0 - t) * Vtemp[i-1][j].x() + t * Vtemp[i-1][j+1].x(); Vtemp[i][j].ry() = (1.0 - t) * Vtemp[i-1][j].y() + t * Vtemp[i-1][j+1].y(); } } if (left) { left->setDegree(deg); for (int j = 0; j <= deg; j++) { left->setPoint(j, Vtemp[j][0]); } } if (right) { right->setDegree(deg); for (int j = 0; j <= deg; j++) { right->setPoint(j, Vtemp[deg-j][j]); } } return (Vtemp[deg][0]); } QList roots(int depth = 0) const { QList rootParams; if (! degree()) return rootParams; // Calculate how often the control polygon crosses the x-axis // This is the upper limit for the number of roots. int xAxisCrossings = controlPolygonZeros(points); if (! xAxisCrossings) { // No solutions. return rootParams; } else if (xAxisCrossings == 1) { // Exactly one solution. // Stop recursion when the tree is deep enough if (depth >= MaxRecursionDepth) { // if deep enough, return 1 solution at midpoint rootParams.append((points.first().x() + points.last().x()) / 2.0); return rootParams; } else if (isFlat(FlatnessTolerance)) { // Calculate intersection of chord with x-axis. QPointF chord = points.last() - points.first(); QPointF segStart = points.first(); rootParams.append((chord.x() * segStart.y() - chord.y() * segStart.x()) / - chord.y()); return rootParams; } } // Many solutions. Do recursive midpoint subdivision. BezierSegment left, right; evaluate(0.5, &left, &right); rootParams += left.roots(depth+1); rootParams += right.roots(depth+1); return rootParams; } static uint controlPolygonZeros(const QList &controlPoints) { int controlPointCount = controlPoints.count(); if (controlPointCount < 2) return 0; int signChanges = 0; int currSign = controlPoints[0].y() < 0.0 ? -1 : 1; int oldSign; for (short i = 1; i < controlPointCount; ++i) { oldSign = currSign; currSign = controlPoints[i].y() < 0.0 ? -1 : 1; if (currSign != oldSign) { ++signChanges; } } return signChanges; } int isFlat (qreal tolerance) const { int deg = degree(); // Find the perpendicular distance from each interior control point to // the line connecting points[0] and points[degree] qreal *distance = new qreal[deg + 1]; // Derive the implicit equation for line connecting first and last control points qreal a = points[0].y() - points[deg].y(); qreal b = points[deg].x() - points[0].x(); qreal c = points[0].x() * points[deg].y() - points[deg].x() * points[0].y(); qreal abSquared = (a * a) + (b * b); for (int i = 1; i < deg; i++) { // Compute distance from each of the points to that line distance[i] = a * points[i].x() + b * points[i].y() + c; if (distance[i] > 0.0) { distance[i] = (distance[i] * distance[i]) / abSquared; } if (distance[i] < 0.0) { distance[i] = -((distance[i] * distance[i]) / abSquared); } } // Find the largest distance qreal max_distance_above = 0.0; qreal max_distance_below = 0.0; for (int i = 1; i < deg; i++) { if (distance[i] < 0.0) { max_distance_below = qMin(max_distance_below, distance[i]); } if (distance[i] > 0.0) { max_distance_above = qMax(max_distance_above, distance[i]); } } delete [] distance; // Implicit equation for zero line qreal a1 = 0.0; qreal b1 = 1.0; qreal c1 = 0.0; // Implicit equation for "above" line qreal a2 = a; qreal b2 = b; qreal c2 = c + max_distance_above; qreal det = a1 * b2 - a2 * b1; qreal dInv = 1.0/det; qreal intercept_1 = (b1 * c2 - b2 * c1) * dInv; // Implicit equation for "below" line a2 = a; b2 = b; c2 = c + max_distance_below; det = a1 * b2 - a2 * b1; dInv = 1.0/det; qreal intercept_2 = (b1 * c2 - b2 * c1) * dInv; // Compute intercepts of bounding box qreal left_intercept = qMin(intercept_1, intercept_2); qreal right_intercept = qMax(intercept_1, intercept_2); qreal error = 0.5 * (right_intercept-left_intercept); return (error < tolerance); } #ifndef NDEBUG void printDebug() const { int index = 0; foreach (const QPointF &p, points) { debugFlake << QString("P%1 ").arg(index++) << p; } } #endif private: QList points; }; class Q_DECL_HIDDEN KoPathSegment::Private { public: Private(KoPathSegment *qq, KoPathPoint *p1, KoPathPoint *p2) : first(p1), second(p2), q(qq) { } /// calculates signed distance of given point from segment chord qreal distanceFromChord(const QPointF &point) const; /// Returns the chord length, i.e. the distance between first and last control point qreal chordLength() const; /// Returns intersection of lines if one exists QList linesIntersection(const KoPathSegment &segment) const; /// Returns parameters for curve extrema QList extrema() const; /// Returns parameters for curve roots QList roots() const; /** * The DeCasteljau algorithm for parameter t. * @param t the parameter to evaluate at * @param p1 the new control point of the segment start (for cubic curves only) * @param p2 the first control point at t * @param p3 the new point at t * @param p4 the second control point at t * @param p3 the new control point of the segment end (for cubic curbes only) */ void deCasteljau(qreal t, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4, QPointF *p5) const; KoPathPoint *first; KoPathPoint *second; KoPathSegment *q; }; void KoPathSegment::Private::deCasteljau(qreal t, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4, QPointF *p5) const { if (!q->isValid()) return; int deg = q->degree(); QPointF q[4]; q[0] = first->point(); if (deg == 2) { q[1] = first->activeControlPoint2() ? first->controlPoint2() : second->controlPoint1(); } else if (deg == 3) { q[1] = first->controlPoint2(); q[2] = second->controlPoint1(); } - q[deg] = second->point(); + if (deg >= 0) { + q[deg] = second->point(); + } // points of the new segment after the split point QPointF p[3]; // the De Casteljau algorithm for (unsigned short j = 1; j <= deg; ++j) { for (unsigned short i = 0; i <= deg - j; ++i) { q[i] = (1.0 - t) * q[i] + t * q[i + 1]; } p[j - 1] = q[0]; } if (deg == 2) { if (p2) *p2 = p[0]; if (p3) *p3 = p[1]; if (p4) *p4 = q[1]; } else if (deg == 3) { if (p1) *p1 = p[0]; if (p2) *p2 = p[1]; if (p3) *p3 = p[2]; if (p4) *p4 = q[1]; if (p5) *p5 = q[2]; } } QList KoPathSegment::Private::roots() const { QList rootParams; if (!q->isValid()) return rootParams; // Calculate how often the control polygon crosses the x-axis // This is the upper limit for the number of roots. int xAxisCrossings = BezierSegment::controlPolygonZeros(q->controlPoints()); if (!xAxisCrossings) { // No solutions. } else if (xAxisCrossings == 1 && q->isFlat(0.01 / chordLength())) { // Exactly one solution. // Calculate intersection of chord with x-axis. QPointF chord = second->point() - first->point(); QPointF segStart = first->point(); rootParams.append((chord.x() * segStart.y() - chord.y() * segStart.x()) / - chord.y()); } else { // Many solutions. Do recursive midpoint subdivision. QPair splitSegments = q->splitAt(0.5); rootParams += splitSegments.first.d->roots(); rootParams += splitSegments.second.d->roots(); } return rootParams; } QList KoPathSegment::Private::extrema() const { int deg = q->degree(); if (deg <= 1) return QList(); QList params; /* * The basic idea for calculating the extrama for bezier segments * was found in comp.graphics.algorithms: * * Both the x coordinate and the y coordinate are polynomial. Newton told * us that at a maximum or minimum the derivative will be zero. * * We have a helpful trick for the derivatives: use the curve r(t) defined by * differences of successive control points. * Setting r(t) to zero and using the x and y coordinates of differences of * successive control points lets us find the parameters t, where the original * bezier curve has a minimum or a maximum. */ if (deg == 2) { /* * For quadratic bezier curves r(t) is a linear Bezier curve: * * 1 * r(t) = Sum Bi,1(t) *Pi = B0,1(t) * P0 + B1,1(t) * P1 * i=0 * * r(t) = (1-t) * P0 + t * P1 * * r(t) = (P1 - P0) * t + P0 */ // calculating the differences between successive control points QPointF cp = first->activeControlPoint2() ? first->controlPoint2() : second->controlPoint1(); QPointF x0 = cp - first->point(); QPointF x1 = second->point() - cp; // calculating the coefficients QPointF a = x1 - x0; QPointF c = x0; if (a.x() != 0.0) params.append(-c.x() / a.x()); if (a.y() != 0.0) params.append(-c.y() / a.y()); } else if (deg == 3) { /* * For cubic bezier curves r(t) is a quadratic Bezier curve: * * 2 * r(t) = Sum Bi,2(t) *Pi = B0,2(t) * P0 + B1,2(t) * P1 + B2,2(t) * P2 * i=0 * * r(t) = (1-t)^2 * P0 + 2t(1-t) * P1 + t^2 * P2 * * r(t) = (P2 - 2*P1 + P0) * t^2 + (2*P1 - 2*P0) * t + P0 * */ // calculating the differences between successive control points QPointF x0 = first->controlPoint2() - first->point(); QPointF x1 = second->controlPoint1() - first->controlPoint2(); QPointF x2 = second->point() - second->controlPoint1(); // calculating the coefficients QPointF a = x2 - 2.0 * x1 + x0; QPointF b = 2.0 * x1 - 2.0 * x0; QPointF c = x0; // calculating parameter t at minimum/maximum in x-direction if (a.x() == 0.0) { params.append(- c.x() / b.x()); } else { qreal rx = b.x() * b.x() - 4.0 * a.x() * c.x(); if (rx < 0.0) rx = 0.0; params.append((-b.x() + sqrt(rx)) / (2.0*a.x())); params.append((-b.x() - sqrt(rx)) / (2.0*a.x())); } // calculating parameter t at minimum/maximum in y-direction if (a.y() == 0.0) { params.append(- c.y() / b.y()); } else { qreal ry = b.y() * b.y() - 4.0 * a.y() * c.y(); if (ry < 0.0) ry = 0.0; params.append((-b.y() + sqrt(ry)) / (2.0*a.y())); params.append((-b.y() - sqrt(ry)) / (2.0*a.y())); } } return params; } qreal KoPathSegment::Private::distanceFromChord(const QPointF &point) const { // the segments chord QPointF chord = second->point() - first->point(); // the point relative to the segment QPointF relPoint = point - first->point(); // project point to chord qreal scale = chord.x() * relPoint.x() + chord.y() * relPoint.y(); scale /= chord.x() * chord.x() + chord.y() * chord.y(); // the vector form the point to the projected point on the chord QPointF diffVec = scale * chord - relPoint; // the unsigned distance of the point to the chord qreal distance = sqrt(diffVec.x() * diffVec.x() + diffVec.y() * diffVec.y()); // determine sign of the distance using the cross product if (chord.x()*relPoint.y() - chord.y()*relPoint.x() > 0) { return distance; } else { return -distance; } } qreal KoPathSegment::Private::chordLength() const { QPointF chord = second->point() - first->point(); return sqrt(chord.x() * chord.x() + chord.y() * chord.y()); } QList KoPathSegment::Private::linesIntersection(const KoPathSegment &segment) const { //debugFlake << "intersecting two lines"; /* we have to line segments: s1 = A + r * (B-A), s2 = C + s * (D-C) for r,s in [0,1] if s1 and s2 intersect we set s1 = s2 so we get these two equations: Ax + r * (Bx-Ax) = Cx + s * (Dx-Cx) Ay + r * (By-Ay) = Cy + s * (Dy-Cy) which we can solve to get r and s */ QList isects; QPointF A = first->point(); QPointF B = second->point(); QPointF C = segment.first()->point(); QPointF D = segment.second()->point(); qreal denom = (B.x() - A.x()) * (D.y() - C.y()) - (B.y() - A.y()) * (D.x() - C.x()); qreal num_r = (A.y() - C.y()) * (D.x() - C.x()) - (A.x() - C.x()) * (D.y() - C.y()); // check if lines are collinear if (denom == 0.0 && num_r == 0.0) return isects; qreal num_s = (A.y() - C.y()) * (B.x() - A.x()) - (A.x() - C.x()) * (B.y() - A.y()); qreal r = num_r / denom; qreal s = num_s / denom; // check if intersection is inside our line segments if (r < 0.0 || r > 1.0) return isects; if (s < 0.0 || s > 1.0) return isects; // calculate the actual intersection point isects.append(A + r * (B - A)); return isects; } /////////////////// KoPathSegment::KoPathSegment(KoPathPoint * first, KoPathPoint * second) : d(new Private(this, first, second)) { } KoPathSegment::KoPathSegment(const KoPathSegment & segment) : d(new Private(this, 0, 0)) { if (! segment.first() || segment.first()->parent()) setFirst(segment.first()); else setFirst(new KoPathPoint(*segment.first())); if (! segment.second() || segment.second()->parent()) setSecond(segment.second()); else setSecond(new KoPathPoint(*segment.second())); } KoPathSegment::KoPathSegment(const QPointF &p0, const QPointF &p1) : d(new Private(this, new KoPathPoint(), new KoPathPoint())) { d->first->setPoint(p0); d->second->setPoint(p1); } KoPathSegment::KoPathSegment(const QPointF &p0, const QPointF &p1, const QPointF &p2) : d(new Private(this, new KoPathPoint(), new KoPathPoint())) { d->first->setPoint(p0); d->first->setControlPoint2(p1); d->second->setPoint(p2); } KoPathSegment::KoPathSegment(const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3) : d(new Private(this, new KoPathPoint(), new KoPathPoint())) { d->first->setPoint(p0); d->first->setControlPoint2(p1); d->second->setControlPoint1(p2); d->second->setPoint(p3); } KoPathSegment &KoPathSegment::operator=(const KoPathSegment &rhs) { if (this == &rhs) return (*this); if (! rhs.first() || rhs.first()->parent()) setFirst(rhs.first()); else setFirst(new KoPathPoint(*rhs.first())); if (! rhs.second() || rhs.second()->parent()) setSecond(rhs.second()); else setSecond(new KoPathPoint(*rhs.second())); return (*this); } KoPathSegment::~KoPathSegment() { if (d->first && ! d->first->parent()) delete d->first; if (d->second && ! d->second->parent()) delete d->second; delete d; } KoPathPoint *KoPathSegment::first() const { return d->first; } void KoPathSegment::setFirst(KoPathPoint *first) { if (d->first && !d->first->parent()) delete d->first; d->first = first; } KoPathPoint *KoPathSegment::second() const { return d->second; } void KoPathSegment::setSecond(KoPathPoint *second) { if (d->second && !d->second->parent()) delete d->second; d->second = second; } bool KoPathSegment::isValid() const { return (d->first && d->second); } bool KoPathSegment::operator==(const KoPathSegment &rhs) const { if (!isValid() && !rhs.isValid()) return true; if (isValid() && !rhs.isValid()) return false; if (!isValid() && rhs.isValid()) return false; return (*first() == *rhs.first() && *second() == *rhs.second()); } int KoPathSegment::degree() const { if (!d->first || !d->second) return -1; bool c1 = d->first->activeControlPoint2(); bool c2 = d->second->activeControlPoint1(); if (!c1 && !c2) return 1; if (c1 && c2) return 3; return 2; } QPointF KoPathSegment::pointAt(qreal t) const { if (!isValid()) return QPointF(); if (degree() == 1) { return d->first->point() + t * (d->second->point() - d->first->point()); } else { QPointF splitP; d->deCasteljau(t, 0, 0, &splitP, 0, 0); return splitP; } } QRectF KoPathSegment::controlPointRect() const { if (!isValid()) return QRectF(); QList points = controlPoints(); QRectF bbox(points.first(), points.first()); Q_FOREACH (const QPointF &p, points) { bbox.setLeft(qMin(bbox.left(), p.x())); bbox.setRight(qMax(bbox.right(), p.x())); bbox.setTop(qMin(bbox.top(), p.y())); bbox.setBottom(qMax(bbox.bottom(), p.y())); } if (degree() == 1) { // adjust bounding rect of horizontal and vertical lines if (bbox.height() == 0.0) bbox.setHeight(0.1); if (bbox.width() == 0.0) bbox.setWidth(0.1); } return bbox; } QRectF KoPathSegment::boundingRect() const { if (!isValid()) return QRectF(); QRectF rect = QRectF(d->first->point(), d->second->point()).normalized(); if (degree() == 1) { // adjust bounding rect of horizontal and vertical lines if (rect.height() == 0.0) rect.setHeight(0.1); if (rect.width() == 0.0) rect.setWidth(0.1); } else { /* * The basic idea for calculating the axis aligned bounding box (AABB) for bezier segments * was found in comp.graphics.algorithms: * Use the points at the extrema of the curve to calculate the AABB. */ foreach (qreal t, d->extrema()) { if (t >= 0.0 && t <= 1.0) { QPointF p = pointAt(t); rect.setLeft(qMin(rect.left(), p.x())); rect.setRight(qMax(rect.right(), p.x())); rect.setTop(qMin(rect.top(), p.y())); rect.setBottom(qMax(rect.bottom(), p.y())); } } } return rect; } QList KoPathSegment::intersections(const KoPathSegment &segment) const { // this function uses a technique known as bezier clipping to find the // intersections of the two bezier curves QList isects; if (!isValid() || !segment.isValid()) return isects; int degree1 = degree(); int degree2 = segment.degree(); QRectF myBound = boundingRect(); QRectF otherBound = segment.boundingRect(); //debugFlake << "my boundingRect =" << myBound; //debugFlake << "other boundingRect =" << otherBound; if (!myBound.intersects(otherBound)) { //debugFlake << "segments do not intersect"; return isects; } // short circuit lines intersection if (degree1 == 1 && degree2 == 1) { //debugFlake << "intersecting two lines"; isects += d->linesIntersection(segment); return isects; } // first calculate the fat line L by using the signed distances // of the control points from the chord qreal dmin, dmax; if (degree1 == 1) { dmin = 0.0; dmax = 0.0; } else if (degree1 == 2) { qreal d1; if (d->first->activeControlPoint2()) d1 = d->distanceFromChord(d->first->controlPoint2()); else d1 = d->distanceFromChord(d->second->controlPoint1()); dmin = qMin(qreal(0.0), qreal(0.5 * d1)); dmax = qMax(qreal(0.0), qreal(0.5 * d1)); } else { qreal d1 = d->distanceFromChord(d->first->controlPoint2()); qreal d2 = d->distanceFromChord(d->second->controlPoint1()); if (d1*d2 > 0.0) { dmin = 0.75 * qMin(qreal(0.0), qMin(d1, d2)); dmax = 0.75 * qMax(qreal(0.0), qMax(d1, d2)); } else { dmin = 4.0 / 9.0 * qMin(qreal(0.0), qMin(d1, d2)); dmax = 4.0 / 9.0 * qMax(qreal(0.0), qMax(d1, d2)); } } //debugFlake << "using fat line: dmax =" << dmax << " dmin =" << dmin; /* the other segment is given as a bezier curve of the form: (1) P(t) = sum_i P_i * B_{n,i}(t) our chord line is of the form: (2) ax + by + c = 0 we can determine the distance d(t) from any point P(t) to our chord by substituting formula (1) into formula (2): d(t) = sum_i d_i B_{n,i}(t), where d_i = a*x_i + b*y_i + c which forms another explicit bezier curve D(t) = (t,d(t)) = sum_i D_i B_{n,i}(t) now values of t for which P(t) lies outside of our fat line L corresponds to values of t for which D(t) lies above d = dmax or below d = dmin we can determine parameter ranges of t for which P(t) is guaranteed to lie outside of L by identifying ranges of t which the convex hull of D(t) lies above dmax or below dmin */ // now calculate the control points of D(t) by using the signed // distances of P_i to our chord KoPathSegment dt; if (degree2 == 1) { QPointF p0(0.0, d->distanceFromChord(segment.first()->point())); QPointF p1(1.0, d->distanceFromChord(segment.second()->point())); dt = KoPathSegment(p0, p1); } else if (degree2 == 2) { QPointF p0(0.0, d->distanceFromChord(segment.first()->point())); QPointF p1 = segment.first()->activeControlPoint2() ? QPointF(0.5, d->distanceFromChord(segment.first()->controlPoint2())) : QPointF(0.5, d->distanceFromChord(segment.second()->controlPoint1())); QPointF p2(1.0, d->distanceFromChord(segment.second()->point())); dt = KoPathSegment(p0, p1, p2); } else if (degree2 == 3) { QPointF p0(0.0, d->distanceFromChord(segment.first()->point())); QPointF p1(1. / 3., d->distanceFromChord(segment.first()->controlPoint2())); QPointF p2(2. / 3., d->distanceFromChord(segment.second()->controlPoint1())); QPointF p3(1.0, d->distanceFromChord(segment.second()->point())); dt = KoPathSegment(p0, p1, p2, p3); } else { //debugFlake << "invalid degree of segment -> exiting"; return isects; } // get convex hull of the segment D(t) QList hull = dt.convexHull(); // now calculate intersections with the line y1 = dmin, y2 = dmax // with the convex hull edges int hullCount = hull.count(); qreal tmin = 1.0, tmax = 0.0; bool intersectionsFoundMax = false; bool intersectionsFoundMin = false; for (int i = 0; i < hullCount; ++i) { QPointF p1 = hull[i]; QPointF p2 = hull[(i+1) % hullCount]; //debugFlake << "intersecting hull edge (" << p1 << p2 << ")"; // hull edge is completely above dmax if (p1.y() > dmax && p2.y() > dmax) continue; // hull edge is completely below dmin if (p1.y() < dmin && p2.y() < dmin) continue; if (p1.x() == p2.x()) { // vertical edge bool dmaxIntersection = (dmax < qMax(p1.y(), p2.y()) && dmax > qMin(p1.y(), p2.y())); bool dminIntersection = (dmin < qMax(p1.y(), p2.y()) && dmin > qMin(p1.y(), p2.y())); if (dmaxIntersection || dminIntersection) { tmin = qMin(tmin, p1.x()); tmax = qMax(tmax, p1.x()); if (dmaxIntersection) { intersectionsFoundMax = true; //debugFlake << "found intersection with dmax at " << p1.x() << "," << dmax; } else { intersectionsFoundMin = true; //debugFlake << "found intersection with dmin at " << p1.x() << "," << dmin; } } } else if (p1.y() == p2.y()) { // horizontal line if (p1.y() == dmin || p1.y() == dmax) { if (p1.y() == dmin) { intersectionsFoundMin = true; //debugFlake << "found intersection with dmin at " << p1.x() << "," << dmin; //debugFlake << "found intersection with dmin at " << p2.x() << "," << dmin; } else { intersectionsFoundMax = true; //debugFlake << "found intersection with dmax at " << p1.x() << "," << dmax; //debugFlake << "found intersection with dmax at " << p2.x() << "," << dmax; } tmin = qMin(tmin, p1.x()); tmin = qMin(tmin, p2.x()); tmax = qMax(tmax, p1.x()); tmax = qMax(tmax, p2.x()); } } else { qreal dx = p2.x() - p1.x(); qreal dy = p2.y() - p1.y(); qreal m = dy / dx; qreal n = p1.y() - m * p1.x(); qreal t1 = (dmax - n) / m; if (t1 >= 0.0 && t1 <= 1.0) { tmin = qMin(tmin, t1); tmax = qMax(tmax, t1); intersectionsFoundMax = true; //debugFlake << "found intersection with dmax at " << t1 << "," << dmax; } qreal t2 = (dmin - n) / m; if (t2 >= 0.0 && t2 < 1.0) { tmin = qMin(tmin, t2); tmax = qMax(tmax, t2); intersectionsFoundMin = true; //debugFlake << "found intersection with dmin at " << t2 << "," << dmin; } } } bool intersectionsFound = intersectionsFoundMin && intersectionsFoundMax; //if (intersectionsFound) // debugFlake << "clipping segment to interval [" << tmin << "," << tmax << "]"; if (!intersectionsFound || (1.0 - (tmax - tmin)) <= 0.2) { //debugFlake << "could not clip enough -> split segment"; // we could not reduce the interval significantly // so split the curve and calculate intersections // with the remaining parts QPair parts = splitAt(0.5); if (d->chordLength() < 1e-5) isects += parts.first.second()->point(); else { isects += segment.intersections(parts.first); isects += segment.intersections(parts.second); } } else if (qAbs(tmin - tmax) < 1e-5) { //debugFlake << "Yay, we found an intersection"; // the interval is pretty small now, just calculate the intersection at this point isects.append(segment.pointAt(tmin)); } else { QPair clip1 = segment.splitAt(tmin); //debugFlake << "splitting segment at" << tmin; qreal t = (tmax - tmin) / (1.0 - tmin); QPair clip2 = clip1.second.splitAt(t); //debugFlake << "splitting second part at" << t << "("<first); KoPathPoint * p2 = new KoPathPoint(*d->second); p1->map(matrix); p2->map(matrix); return KoPathSegment(p1, p2); } KoPathSegment KoPathSegment::toCubic() const { if (! isValid()) return KoPathSegment(); KoPathPoint * p1 = new KoPathPoint(*d->first); KoPathPoint * p2 = new KoPathPoint(*d->second); if (degree() == 1) { p1->setControlPoint2(p1->point() + 0.3 * (p2->point() - p1->point())); p2->setControlPoint1(p2->point() + 0.3 * (p1->point() - p2->point())); } else if (degree() == 2) { /* quadric bezier (a0,a1,a2) to cubic bezier (b0,b1,b2,b3): * * b0 = a0 * b1 = a0 + 2/3 * (a1-a0) * b2 = a1 + 1/3 * (a2-a1) * b3 = a2 */ QPointF a1 = p1->activeControlPoint2() ? p1->controlPoint2() : p2->controlPoint1(); QPointF b1 = p1->point() + 2.0 / 3.0 * (a1 - p1->point()); QPointF b2 = a1 + 1.0 / 3.0 * (p2->point() - a1); p1->setControlPoint2(b1); p2->setControlPoint1(b2); } return KoPathSegment(p1, p2); } qreal KoPathSegment::length(qreal error) const { /* * This algorithm is implemented based on an idea by Jens Gravesen: * "Adaptive subdivision and the length of Bezier curves" mat-report no. 1992-10, Mathematical Institute, * The Technical University of Denmark. * * By subdividing the curve at parameter value t you only have to find the length of a full Bezier curve. * If you denote the length of the control polygon by L1 i.e.: * L1 = |P0 P1| +|P1 P2| +|P2 P3| * * and the length of the cord by L0 i.e.: * L0 = |P0 P3| * * then * L = 1/2*L0 + 1/2*L1 * * is a good approximation to the length of the curve, and the difference * ERR = L1-L0 * * is a measure of the error. If the error is to large, then you just subdivide curve at parameter value * 1/2, and find the length of each half. * If m is the number of subdivisions then the error goes to zero as 2^-4m. * If you don't have a cubic curve but a curve of degree n then you put * L = (2*L0 + (n-1)*L1)/(n+1) */ int deg = degree(); if (deg == -1) return 0.0; QList ctrlPoints = controlPoints(); // calculate chord length qreal chordLen = d->chordLength(); if (deg == 1) { return chordLen; } // calculate length of control polygon qreal polyLength = 0.0; for (int i = 0; i < deg; ++i) { QPointF ctrlSegment = ctrlPoints[i+1] - ctrlPoints[i]; polyLength += sqrt(ctrlSegment.x() * ctrlSegment.x() + ctrlSegment.y() * ctrlSegment.y()); } if ((polyLength - chordLen) > error) { // the error is still bigger than our tolerance -> split segment QPair parts = splitAt(0.5); return parts.first.length(error) + parts.second.length(error); } else { // the error is smaller than our tolerance if (deg == 3) return 0.5 * chordLen + 0.5 * polyLength; else return (2.0 * chordLen + polyLength) / 3.0; } } qreal KoPathSegment::lengthAt(qreal t, qreal error) const { if (t == 0.0) return 0.0; if (t == 1.0) return length(error); QPair parts = splitAt(t); return parts.first.length(error); } qreal KoPathSegment::paramAtLength(qreal length, qreal tolerance) const { const int deg = degree(); // invalid degree or invalid specified length if (deg < 1 || length <= 0.0) { return 0.0; } if (deg == 1) { // make sure we return a maximum value of 1.0 return qMin(qreal(1.0), length / d->chordLength()); } // for curves we need to make sure, that the specified length // value does not exceed the actual length of the segment // if that happens, we return 1.0 to avoid infinite looping if (length >= d->chordLength() && length >= this->length(tolerance)) { return 1.0; } qreal startT = 0.0; // interval start qreal midT = 0.5; // interval center qreal endT = 1.0; // interval end // divide and conquer, split a midpoint and check // on which side of the midpoint to continue qreal midLength = lengthAt(0.5); while (qAbs(midLength - length) / length > tolerance) { if (midLength < length) startT = midT; else endT = midT; // new interval center midT = 0.5 * (startT + endT); // length at new interval center midLength = lengthAt(midT); } return midT; } bool KoPathSegment::isFlat(qreal tolerance) const { /* * Calculate the height of the bezier curve. * This is done by rotating the curve so that then chord * is parallel to the x-axis and the calculating the * parameters t for the extrema of the curve. * The curve points at the extrema are then used to * calculate the height. */ if (degree() <= 1) return true; QPointF chord = d->second->point() - d->first->point(); // calculate angle of chord to the x-axis qreal chordAngle = atan2(chord.y(), chord.x()); QTransform m; m.translate(d->first->point().x(), d->first->point().y()); m.rotate(chordAngle * M_PI / 180.0); m.translate(-d->first->point().x(), -d->first->point().y()); KoPathSegment s = mapped(m); qreal minDist = 0.0; qreal maxDist = 0.0; foreach (qreal t, s.d->extrema()) { if (t >= 0.0 && t <= 1.0) { QPointF p = pointAt(t); qreal dist = s.d->distanceFromChord(p); minDist = qMin(dist, minDist); maxDist = qMax(dist, maxDist); } } return (maxDist - minDist <= tolerance); } QList KoPathSegment::convexHull() const { QList hull; int deg = degree(); if (deg == 1) { // easy just the two end points hull.append(d->first->point()); hull.append(d->second->point()); } else if (deg == 2) { // we want to have a counter-clockwise oriented triangle // of the three control points QPointF chord = d->second->point() - d->first->point(); QPointF cp = d->first->activeControlPoint2() ? d->first->controlPoint2() : d->second->controlPoint1(); QPointF relP = cp - d->first->point(); // check on which side of the chord the control point is bool pIsRight = (chord.x() * relP.y() - chord.y() * relP.x() > 0); hull.append(d->first->point()); if (pIsRight) hull.append(cp); hull.append(d->second->point()); if (! pIsRight) hull.append(cp); } else if (deg == 3) { // we want a counter-clockwise oriented polygon QPointF chord = d->second->point() - d->first->point(); QPointF relP1 = d->first->controlPoint2() - d->first->point(); // check on which side of the chord the control points are bool p1IsRight = (chord.x() * relP1.y() - chord.y() * relP1.x() > 0); hull.append(d->first->point()); if (p1IsRight) hull.append(d->first->controlPoint2()); hull.append(d->second->point()); if (! p1IsRight) hull.append(d->first->controlPoint2()); // now we have a counter-clockwise triangle with the points i,j,k // we have to check where the last control points lies bool rightOfEdge[3]; QPointF lastPoint = d->second->controlPoint1(); for (int i = 0; i < 3; ++i) { QPointF relP = lastPoint - hull[i]; QPointF edge = hull[(i+1)%3] - hull[i]; rightOfEdge[i] = (edge.x() * relP.y() - edge.y() * relP.x() > 0); } for (int i = 0; i < 3; ++i) { int prev = (3 + i - 1) % 3; int next = (i + 1) % 3; // check if point is only right of the n-th edge if (! rightOfEdge[prev] && rightOfEdge[i] && ! rightOfEdge[next]) { // insert by breaking the n-th edge hull.insert(i + 1, lastPoint); break; } // check if it is right of the n-th and right of the (n+1)-th edge if (rightOfEdge[i] && rightOfEdge[next]) { // remove both edge, insert two new edges hull[i+1] = lastPoint; break; } // check if it is right of n-th and right of (n-1)-th edge if (rightOfEdge[i] && rightOfEdge[prev]) { hull[i] = lastPoint; break; } } } return hull; } QPair KoPathSegment::splitAt(qreal t) const { QPair results; if (!isValid()) return results; if (degree() == 1) { QPointF p = d->first->point() + t * (d->second->point() - d->first->point()); results.first = KoPathSegment(d->first->point(), p); results.second = KoPathSegment(p, d->second->point()); } else { QPointF newCP2, newCP1, splitP, splitCP1, splitCP2; d->deCasteljau(t, &newCP2, &splitCP1, &splitP, &splitCP2, &newCP1); if (degree() == 2) { if (second()->activeControlPoint1()) { KoPathPoint *s1p1 = new KoPathPoint(0, d->first->point()); KoPathPoint *s1p2 = new KoPathPoint(0, splitP); s1p2->setControlPoint1(splitCP1); KoPathPoint *s2p1 = new KoPathPoint(0, splitP); KoPathPoint *s2p2 = new KoPathPoint(0, d->second->point()); s2p2->setControlPoint1(splitCP2); results.first = KoPathSegment(s1p1, s1p2); results.second = KoPathSegment(s2p1, s2p2); } else { results.first = KoPathSegment(d->first->point(), splitCP1, splitP); results.second = KoPathSegment(splitP, splitCP2, d->second->point()); } } else { results.first = KoPathSegment(d->first->point(), newCP2, splitCP1, splitP); results.second = KoPathSegment(splitP, splitCP2, newCP1, d->second->point()); } } return results; } QList KoPathSegment::controlPoints() const { QList controlPoints; controlPoints.append(d->first->point()); if (d->first->activeControlPoint2()) controlPoints.append(d->first->controlPoint2()); if (d->second->activeControlPoint1()) controlPoints.append(d->second->controlPoint1()); controlPoints.append(d->second->point()); return controlPoints; } qreal KoPathSegment::nearestPoint(const QPointF &point) const { if (!isValid()) return -1.0; const int deg = degree(); // use shortcut for line segments if (deg == 1) { // the segments chord QPointF chord = d->second->point() - d->first->point(); // the point relative to the segment QPointF relPoint = point - d->first->point(); // project point to chord (dot product) qreal scale = chord.x() * relPoint.x() + chord.y() * relPoint.y(); // normalize using the chord length scale /= chord.x() * chord.x() + chord.y() * chord.y(); if (scale < 0.0) { return 0.0; } else if (scale > 1.0) { return 1.0; } else { return scale; } } /* This function solves the "nearest point on curve" problem. That means, it * calculates the point q (to be precise: it's parameter t) on this segment, which * is located nearest to the input point P. * The basic idea is best described (because it is freely available) in "Phoenix: * An Interactive Curve Design System Based on the Automatic Fitting of * Hand-Sketched Curves", Philip J. Schneider (Master thesis, University of * Washington). * * For the nearest point q = C(t) on this segment, the first derivative is * orthogonal to the distance vector "C(t) - P". In other words we are looking for * solutions of f(t) = (C(t) - P) * C'(t) = 0. * (C(t) - P) is a nth degree curve, C'(t) a n-1th degree curve => f(t) is a * (2n - 1)th degree curve and thus has up to 2n - 1 distinct solutions. * We solve the problem f(t) = 0 by using something called "Approximate Inversion Method". * Let's write f(t) explicitly (with c_i = p_i - P and d_j = p_{j+1} - p_j): * * n n-1 * f(t) = SUM c_i * B^n_i(t) * SUM d_j * B^{n-1}_j(t) * i=0 j=0 * * n n-1 * = SUM SUM w_{ij} * B^{2n-1}_{i+j}(t) * i=0 j=0 * * with w_{ij} = c_i * d_j * z_{ij} and * * BinomialCoeff(n, i) * BinomialCoeff(n - i ,j) * z_{ij} = ----------------------------------------------- * BinomialCoeff(2n - 1, i + j) * * This Bernstein-Bezier polynom representation can now be solved for its roots. */ QList ctlPoints = controlPoints(); // Calculate the c_i = point(i) - P. QPointF * c_i = new QPointF[ deg + 1 ]; for (int i = 0; i <= deg; ++i) { c_i[ i ] = ctlPoints[ i ] - point; } // Calculate the d_j = point(j + 1) - point(j). QPointF *d_j = new QPointF[deg]; for (int j = 0; j <= deg - 1; ++j) { d_j[j] = 3.0 * (ctlPoints[j+1] - ctlPoints[j]); } // Calculate the dot products of c_i and d_i. qreal *products = new qreal[deg * (deg + 1)]; for (int j = 0; j <= deg - 1; ++j) { for (int i = 0; i <= deg; ++i) { products[j * (deg + 1) + i] = d_j[j].x() * c_i[i].x() + d_j[j].y() * c_i[i].y(); } } // We don't need the c_i and d_i anymore. delete[] d_j ; delete[] c_i ; // Calculate the control points of the new 2n-1th degree curve. BezierSegment newCurve; newCurve.setDegree(2 * deg - 1); // Set up control points in the (u, f(u))-plane. for (unsigned short u = 0; u <= 2 * deg - 1; ++u) { newCurve.setPoint(u, QPointF(static_cast(u) / static_cast(2 * deg - 1), 0.0)); } // Precomputed "z" for cubics static const qreal z3[3*4] = {1.0, 0.6, 0.3, 0.1, 0.4, 0.6, 0.6, 0.4, 0.1, 0.3, 0.6, 1.0}; // Precomputed "z" for quadrics static const qreal z2[2*3] = {1.0, 2./3., 1./3., 1./3., 2./3., 1.0}; const qreal *z = degree() == 3 ? z3 : z2; // Set f(u)-values. for (int k = 0; k <= 2 * deg - 1; ++k) { int min = qMin(k, deg); for (unsigned short i = qMax(0, k - (deg - 1)); i <= min; ++i) { unsigned short j = k - i; // p_k += products[j][i] * z[j][i]. QPointF currentPoint = newCurve.point(k); currentPoint.ry() += products[j * (deg + 1) + i] * z[j * (deg + 1) + i]; newCurve.setPoint(k, currentPoint); } } // We don't need the c_i/d_i dot products and the z_{ij} anymore. delete[] products; // Find roots. QList rootParams = newCurve.roots(); // Now compare the distances of the candidate points. // First candidate is the previous knot. qreal distanceSquared = kisSquareDistance(point, d->first->point()); qreal minDistanceSquared = distanceSquared; qreal resultParam = 0.0; // Iterate over the found candidate params. foreach (qreal root, rootParams) { distanceSquared = kisSquareDistance(point, pointAt(root)); if (distanceSquared < minDistanceSquared) { minDistanceSquared = distanceSquared; resultParam = root; } } // Last candidate is the knot. distanceSquared = kisSquareDistance(point, d->second->point()); if (distanceSquared < minDistanceSquared) { minDistanceSquared = distanceSquared; resultParam = 1.0; } return resultParam; } KoPathSegment KoPathSegment::interpolate(const QPointF &p0, const QPointF &p1, const QPointF &p2, qreal t) { if (t <= 0.0 || t >= 1.0) return KoPathSegment(); /* B(t) = [x2 y2] = (1-t)^2*P0 + 2t*(1-t)*P1 + t^2*P2 B(t) - (1-t)^2*P0 - t^2*P2 P1 = -------------------------- 2t*(1-t) */ QPointF c1 = p1 - (1.0-t) * (1.0-t)*p0 - t * t * p2; qreal denom = 2.0 * t * (1.0-t); c1.rx() /= denom; c1.ry() /= denom; return KoPathSegment(p0, c1, p2); } #if 0 void KoPathSegment::printDebug() const { int deg = degree(); debugFlake << "degree:" << deg; if (deg < 1) return; debugFlake << "P0:" << d->first->point(); if (deg == 1) { debugFlake << "P2:" << d->second->point(); } else if (deg == 2) { if (d->first->activeControlPoint2()) debugFlake << "P1:" << d->first->controlPoint2(); else debugFlake << "P1:" << d->second->controlPoint1(); debugFlake << "P2:" << d->second->point(); } else if (deg == 3) { debugFlake << "P1:" << d->first->controlPoint2(); debugFlake << "P2:" << d->second->controlPoint1(); debugFlake << "P3:" << d->second->point(); } } #endif diff --git a/libs/flake/KoShapeAnchor.cpp b/libs/flake/KoShapeAnchor.cpp index 19ce940ad7..30288c0641 100644 --- a/libs/flake/KoShapeAnchor.cpp +++ b/libs/flake/KoShapeAnchor.cpp @@ -1,527 +1,528 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009-2010 Thomas Zander * Copyright (C) 2010 Ko Gmbh * Copyright (C) 2011 Matus Hanzes * Copyright (C) 2013 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 "KoShapeAnchor.h" #include "KoStyleStack.h" #include "KoOdfLoadingContext.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoShapeAnchor::Private { public: Private(KoShape *s) : shape(s) , verticalPos(KoShapeAnchor::VTop) , verticalRel(KoShapeAnchor::VLine) , horizontalPos(KoShapeAnchor::HLeft) , horizontalRel(KoShapeAnchor::HChar) , flowWithText(true) , anchorType(KoShapeAnchor::AnchorToCharacter) , placementStrategy(0) , pageNumber(-1) , textLocation(0) { } QDebug printDebug(QDebug dbg) const { #ifndef NDEBUG dbg.space() << "KoShapeAnchor" << this; dbg.space() << "offset:" << offset; dbg.space() << "shape:" << shape->name(); #endif return dbg.space(); } KoShape * const shape; QPointF offset; KoShapeAnchor::VerticalPos verticalPos; KoShapeAnchor::VerticalRel verticalRel; KoShapeAnchor::HorizontalPos horizontalPos; KoShapeAnchor::HorizontalRel horizontalRel; QString wrapInfluenceOnPosition; bool flowWithText; KoShapeAnchor::AnchorType anchorType; KoShapeAnchor::PlacementStrategy *placementStrategy; int pageNumber; KoShapeAnchor::TextLocation *textLocation; }; KoShapeAnchor::KoShapeAnchor(KoShape *shape) : d(new Private(shape)) { } KoShapeAnchor::~KoShapeAnchor() { if (d->placementStrategy != 0) { delete d->placementStrategy; } + delete d; } KoShape *KoShapeAnchor::shape() const { return d->shape; } KoShapeAnchor::AnchorType KoShapeAnchor::anchorType() const { return d->anchorType; } void KoShapeAnchor::setHorizontalPos(HorizontalPos hp) { d->horizontalPos = hp; } KoShapeAnchor::HorizontalPos KoShapeAnchor::horizontalPos() const { return d->horizontalPos; } void KoShapeAnchor::setHorizontalRel(HorizontalRel hr) { d->horizontalRel = hr; } KoShapeAnchor::HorizontalRel KoShapeAnchor::horizontalRel() const { return d->horizontalRel; } void KoShapeAnchor::setVerticalPos(VerticalPos vp) { d->verticalPos = vp; } KoShapeAnchor::VerticalPos KoShapeAnchor::verticalPos() const { return d->verticalPos; } void KoShapeAnchor::setVerticalRel(VerticalRel vr) { d->verticalRel = vr; } KoShapeAnchor::VerticalRel KoShapeAnchor::verticalRel() const { return d->verticalRel; } QString KoShapeAnchor::wrapInfluenceOnPosition() const { return d->wrapInfluenceOnPosition; } bool KoShapeAnchor::flowWithText() const { return d->flowWithText; } int KoShapeAnchor::pageNumber() const { return d->pageNumber; } const QPointF &KoShapeAnchor::offset() const { return d->offset; } void KoShapeAnchor::setOffset(const QPointF &offset) { d->offset = offset; } void KoShapeAnchor::saveOdf(KoShapeSavingContext &context) const { // anchor-type switch (d->anchorType) { case AnchorToCharacter: shape()->setAdditionalAttribute("text:anchor-type", "char"); break; case AnchorAsCharacter: shape()->setAdditionalAttribute("text:anchor-type", "as-char"); break; case AnchorParagraph: shape()->setAdditionalAttribute("text:anchor-type", "paragraph"); break; case AnchorPage: shape()->setAdditionalAttribute("text:anchor-type", "page"); break; default: break; } // vertical-pos switch (d->verticalPos) { case VBelow: shape()->setAdditionalStyleAttribute("style:vertical-pos", "below"); break; case VBottom: shape()->setAdditionalStyleAttribute("style:vertical-pos", "bottom"); break; case VFromTop: shape()->setAdditionalStyleAttribute("style:vertical-pos", "from-top"); break; case VMiddle: shape()->setAdditionalStyleAttribute("style:vertical-pos", "middle"); break; case VTop: shape()->setAdditionalStyleAttribute("style:vertical-pos", "top"); break; default: break; } // vertical-rel switch (d->verticalRel) { case VBaseline: shape()->setAdditionalStyleAttribute("style:vertical-rel", "baseline"); break; case VChar: shape()->setAdditionalStyleAttribute("style:vertical-rel", "char"); break; case VFrame: shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame"); break; case VFrameContent: shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame-content"); break; case VLine: shape()->setAdditionalStyleAttribute("style:vertical-rel", "line"); break; case VPage: shape()->setAdditionalStyleAttribute("style:vertical-rel", "page"); break; case VPageContent: shape()->setAdditionalStyleAttribute("style:vertical-rel", "page-content"); break; case VParagraph: shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph"); break; case VParagraphContent: shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph-content"); break; case VText: shape()->setAdditionalStyleAttribute("style:vertical-rel", "text"); break; default: break; } // horizontal-pos switch (d->horizontalPos) { case HCenter: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "center"); break; case HFromInside: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-inside"); break; case HFromLeft: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-left"); break; case HInside: shape()->setAdditionalStyleAttribute("style:horizontal-posl", "inside"); break; case HLeft: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "left"); break; case HOutside: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "outside"); break; case HRight: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "right"); break; default: break; } // horizontal-rel switch (d->horizontalRel) { case HChar: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "char"); break; case HPage: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page"); break; case HPageContent: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-content"); break; case HPageStartMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-start-margin"); break; case HPageEndMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-end-margin"); break; case HFrame: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame"); break; case HFrameContent: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-content"); break; case HFrameEndMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-end-margin"); break; case HFrameStartMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-start-margin"); break; case HParagraph: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph"); break; case HParagraphContent: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-content"); break; case HParagraphEndMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-end-margin"); break; case HParagraphStartMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-start-margin"); break; default: break; } if (!d->wrapInfluenceOnPosition.isEmpty()) { shape()->setAdditionalStyleAttribute("draw:wrap-influence-on-position", d->wrapInfluenceOnPosition); } if (d->flowWithText) { shape()->setAdditionalStyleAttribute("style:flow-with-text", "true"); } else { shape()->setAdditionalStyleAttribute("style:flow-with-text", "false"); } if (shape()->parent()) {// an anchor may not yet have been layout-ed QTransform parentMatrix = shape()->parent()->absoluteTransformation(0).inverted(); QTransform shapeMatrix = shape()->absoluteTransformation(0); qreal dx = d->offset.x() - shapeMatrix.dx()*parentMatrix.m11() - shapeMatrix.dy()*parentMatrix.m21(); qreal dy = d->offset.y() - shapeMatrix.dx()*parentMatrix.m12() - shapeMatrix.dy()*parentMatrix.m22(); context.addShapeOffset(shape(), QTransform(parentMatrix.m11(),parentMatrix.m12(), parentMatrix.m21(),parentMatrix.m22(), dx,dy)); } shape()->saveOdf(context); context.removeShapeOffset(shape()); } bool KoShapeAnchor::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { d->offset = shape()->position(); QString anchorType = shape()->additionalAttribute("text:anchor-type"); if (anchorType == "char") { d->anchorType = AnchorToCharacter; } else if (anchorType == "as-char") { d->anchorType = AnchorAsCharacter; d->horizontalRel = HChar; d->horizontalPos = HLeft; } else if (anchorType == "paragraph") { d->anchorType = AnchorParagraph; } else { d->anchorType = AnchorPage; // it has different defaults at least LO thinks so - ODF doesn't define defaults for this d->horizontalPos = HFromLeft; d->verticalPos = VFromTop; d->horizontalRel = HPage; d->verticalRel = VPage; } if (anchorType == "page" && shape()->hasAdditionalAttribute("text:anchor-page-number")) { d->pageNumber = shape()->additionalAttribute("text:anchor-page-number").toInt(); if (d->pageNumber <= 0) { // invalid if the page-number is invalid (OO.org does the same) // see http://bugs.kde.org/show_bug.cgi?id=281869 d->pageNumber = -1; } } else { d->pageNumber = -1; } // always make it invisible or it will create empty rects on the first page // during initial layout. This is because only when we layout it's final page is // the shape moved away from page 1 // in KWRootAreaProvider of textlayout it's set back to visible shape()->setVisible(false); // load settings from graphic style KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); styleStack.setTypeProperties("graphic"); } QString verticalPos = styleStack.property(KoXmlNS::style, "vertical-pos"); QString verticalRel = styleStack.property(KoXmlNS::style, "vertical-rel"); QString horizontalPos = styleStack.property(KoXmlNS::style, "horizontal-pos"); QString horizontalRel = styleStack.property(KoXmlNS::style, "horizontal-rel"); d->wrapInfluenceOnPosition = styleStack.property(KoXmlNS::draw, "wrap-influence-on-position"); QString flowWithText = styleStack.property(KoXmlNS::style, "flow-with-text"); d->flowWithText = flowWithText.isEmpty() ? false : flowWithText == "true"; styleStack.restore(); // vertical-pos if (verticalPos == "below") {//svg:y attribute is ignored d->verticalPos = VBelow; d->offset.setY(0); } else if (verticalPos == "bottom") {//svg:y attribute is ignored d->verticalPos = VBottom; d->offset.setY(-shape()->size().height()); } else if (verticalPos == "from-top") { d->verticalPos = VFromTop; } else if (verticalPos == "middle") {//svg:y attribute is ignored d->verticalPos = VMiddle; d->offset.setY(-(shape()->size().height()/2)); } else if (verticalPos == "top") {//svg:y attribute is ignored d->verticalPos = VTop; d->offset.setY(0); } // vertical-rel if (verticalRel == "baseline") d->verticalRel = VBaseline; else if (verticalRel == "char") d->verticalRel = VChar; else if (verticalRel == "frame") d->verticalRel = VFrame; else if (verticalRel == "frame-content") d->verticalRel = VFrameContent; else if (verticalRel == "line") d->verticalRel = VLine; else if (verticalRel == "page") d->verticalRel = VPage; else if (verticalRel == "page-content") d->verticalRel = VPageContent; else if (verticalRel == "paragraph") d->verticalRel = VParagraph; else if (verticalRel == "paragraph-content") d->verticalRel = VParagraphContent; else if (verticalRel == "text") d->verticalRel = VText; // horizontal-pos if (horizontalPos == "center") {//svg:x attribute is ignored d->horizontalPos = HCenter; d->offset.setX(-(shape()->size().width()/2)); } else if (horizontalPos == "from-inside") { d->horizontalPos = HFromInside; } else if (horizontalPos == "from-left") { d->horizontalPos = HFromLeft; } else if (horizontalPos == "inside") {//svg:x attribute is ignored d->horizontalPos = HInside; d->offset.setX(0); } else if (horizontalPos == "left") {//svg:x attribute is ignored d->horizontalPos = HLeft; d->offset.setX(0); }else if (horizontalPos == "outside") {//svg:x attribute is ignored d->horizontalPos = HOutside; d->offset.setX(-shape()->size().width()); }else if (horizontalPos == "right") {//svg:x attribute is ignored d->horizontalPos = HRight; d->offset.setX(-shape()->size().width()); } // horizontal-rel if (horizontalRel == "char") d->horizontalRel = HChar; else if (horizontalRel == "page") d->horizontalRel = HPage; else if (horizontalRel == "page-content") d->horizontalRel = HPageContent; else if (horizontalRel == "page-start-margin") d->horizontalRel = HPageStartMargin; else if (horizontalRel == "page-end-margin") d->horizontalRel = HPageEndMargin; else if (horizontalRel == "frame") d->horizontalRel = HFrame; else if (horizontalRel == "frame-content") d->horizontalRel = HFrameContent; else if (horizontalRel == "frame-end-margin") d->horizontalRel = HFrameEndMargin; else if (horizontalRel == "frame-start-margin") d->horizontalRel = HFrameStartMargin; else if (horizontalRel == "paragraph") d->horizontalRel = HParagraph; else if (horizontalRel == "paragraph-content") d->horizontalRel = HParagraphContent; else if (horizontalRel == "paragraph-end-margin") d->horizontalRel = HParagraphEndMargin; else if (horizontalRel == "paragraph-start-margin") d->horizontalRel = HParagraphStartMargin; // if svg:x or svg:y should be ignored set new position shape()->setPosition(d->offset); return true; } void KoShapeAnchor::setAnchorType(KoShapeAnchor::AnchorType type) { d->anchorType = type; if (type == AnchorAsCharacter) { d->horizontalRel = HChar; d->horizontalPos = HLeft; } } KoShapeAnchor::TextLocation *KoShapeAnchor::textLocation() const { return d->textLocation; } void KoShapeAnchor::setTextLocation(TextLocation *textLocation) { d->textLocation = textLocation; } KoShapeAnchor::PlacementStrategy *KoShapeAnchor::placementStrategy() const { return d->placementStrategy; } void KoShapeAnchor::setPlacementStrategy(PlacementStrategy *placementStrategy) { if (placementStrategy != d->placementStrategy) { delete d->placementStrategy; d->placementStrategy = placementStrategy; } } diff --git a/libs/global/kis_signal_compressor.h b/libs/global/kis_signal_compressor.h index 1b6b9db84b..9252bb0b6d 100644 --- a/libs/global/kis_signal_compressor.h +++ b/libs/global/kis_signal_compressor.h @@ -1,102 +1,102 @@ /* * 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. */ #ifndef __KIS_SIGNAL_COMPRESSOR_H #define __KIS_SIGNAL_COMPRESSOR_H #include #include "kritaglobal_export.h" #include class QTimer; /** * Sets a timer to delay or throttle activation of a Qt slot. One example of * where this is used is to limit the amount of expensive redraw activity on the * canvas. * - * There are three behaviors to choose from. + * There are four behaviors to choose from. * * POSTPONE resets the timer after each call. Therefore if the calls are made * quickly enough, the timer will never be activated. * * FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all - * the other actions the other action like in POSTPONE. This mode is - * used e.g. in move/remove layer functionality. If you remove a + * the other actions like in POSTPONE. This mode is + * used e.g. in move/remove layer functionality. If you remove a * single layer, you'll see the result immediately. But if you want to * remove multiple layers, you should wait until all the actions are * finished. * * FIRST_ACTIVE emits the timeout() event immediately and sets a timer of * duration \p delay. If the compressor is triggered during this time, it will * wait until the end of the delay period to fire the signal. Further events are * ignored until the timer elapses. Think of it as a queue with size 1, and * where the leading element is popped every \p delay ms. * * FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p * delay ms. The compressor becomes inactive and all events are ignored until * the timer has elapsed. * * The current implementation allows the timeout() to be delayed by up to 2 times * \p delay in certain situations (for details see cpp file). */ class KRITAGLOBAL_EXPORT KisSignalCompressor : public QObject { Q_OBJECT public: enum Mode { POSTPONE, /* Calling start() resets the timer to \p delay ms */ FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */ FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */ FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */ UNDEFINED /* KisSignalCompressor is created without an explicit mode */ }; public: KisSignalCompressor(); KisSignalCompressor(int delay, Mode mode, QObject *parent = 0); bool isActive() const; void setMode(Mode mode); public Q_SLOTS: void setDelay(int delay); void start(); void stop(); private Q_SLOTS: void slotTimerExpired(); Q_SIGNALS: void timeout(); private: bool tryEmitOnTick(bool isFromTimer); bool tryEmitSignalSafely(); private: QTimer *m_timer = 0; Mode m_mode = UNDEFINED; bool m_signalsPending = false; QElapsedTimer m_lastEmittedTimer; int m_isEmitting = 0; }; #endif /* __KIS_SIGNAL_COMPRESSOR_H */ diff --git a/libs/image/3rdparty/einspline/nubspline_create.cpp b/libs/image/3rdparty/einspline/nubspline_create.cpp index 4a2ee65429..3cc20f40c2 100644 --- a/libs/image/3rdparty/einspline/nubspline_create.cpp +++ b/libs/image/3rdparty/einspline/nubspline_create.cpp @@ -1,1110 +1,1117 @@ ///////////////////////////////////////////////////////////////////////////// // einspline: a library for creating and evaluating B-splines // // Copyright (C) 2007 Kenneth P. Esler, Jr. // // // // 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 "nubspline_create.h" #include #include #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #ifndef __USE_XOPEN2K #define __USE_XOPEN2K #endif #include #include //////////////////////////////////////////////////////// // Notes on conventions: // // Below, M (and Mx, My, Mz) represent the number of // // data points to be interpolated. With derivative // // boundary conditions, it is equal to the number of // // grid points. With periodic boundary conditions, // // it is one less than the number of grid points. // // N (and Nx, Ny, Nz) is the number of B-spline // // coefficients, which is #(grid points)+2 for all // // boundary conditions. // //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //// Single-precision real creation routines //// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// void solve_NUB_deriv_interp_1d_s (NUBasis* restrict basis, float* restrict data, int datastride, float* restrict p, int pstride, float abcdInitial[4], float abcdFinal[4]) { int M = basis->grid->num_points; int N = M+2; // Banded matrix storage. The first three elements in the // tinyvector store the tridiagonal coefficients. The last element // stores the RHS data. #ifdef HAVE_C_VARARRAYS float bands[4*N]; #else float *bands = new float[4*N]; #endif // Fill up bands for (int i=0; i<4; i++) { bands[i] = abcdInitial[i]; bands[4*(N-1)+i] = abcdFinal[i]; } for (int i=0; i0; row--) p[pstride*(row)] = bands[4*(row)+3] - bands[4*(row)+2]*p[pstride*(row+1)]; // Finish with first row p[0] = bands[4*(0)+3] - bands[4*(0)+1]*p[pstride*1] - bands[4*(0)+2]*p[pstride*2]; #ifndef HAVE_C_VARARRAYS delete[] bands; #endif } // The number of elements in data should be one less than the number // of grid points void solve_NUB_periodic_interp_1d_s (NUBasis* restrict basis, float* restrict data, int datastride, float* restrict p, int pstride) { int M = basis->grid->num_points-1; // Banded matrix storage. The first three elements in each row // store the tridiagonal coefficients. The last element // stores the RHS data. #ifdef HAVE_C_VARARRAYS float bands[4*M], lastCol[M]; #else float *bands = new float[4*M]; float *lastCol = new float[ M]; #endif // Fill up bands for (int i=0; i=0; row--) p[pstride*(row+1)] = bands[4*(row)+3] - bands[4*(row)+2]*p[pstride*(row+2)] - lastCol[row]*p[pstride*M]; p[pstride* 0 ] = p[pstride*M]; p[pstride*(M+1)] = p[pstride*1]; p[pstride*(M+2)] = p[pstride*2]; #ifndef HAVE_C_VARARRAYS delete[] bands; delete[] lastCol; #endif } void find_NUBcoefs_1d_s (NUBasis* restrict basis, BCtype_s bc, float *data, int dstride, float *coefs, int cstride) { if (bc.lCode == PERIODIC) solve_NUB_periodic_interp_1d_s (basis, data, dstride, coefs, cstride); else { int M = basis->grid->num_points; // Setup boundary conditions - float bfuncs[4], dbfuncs[4], abcd_left[4], abcd_right[4]; + float bfuncs[4] = {}; + float dbfuncs[4] = {}; + float abcd_left[4] = {}; + float abcd_right[4] = {}; // Left boundary if (bc.lCode == FLAT || bc.lCode == NATURAL) bc.lVal = 0.0; if (bc.lCode == FLAT || bc.lCode == DERIV1) { get_NUBasis_dfuncs_si (basis, 0, bfuncs, abcd_left); abcd_left[3] = bc.lVal; } if (bc.lCode == NATURAL || bc.lCode == DERIV2) { get_NUBasis_d2funcs_si (basis, 0, bfuncs, dbfuncs, abcd_left); abcd_left[3] = bc.lVal; } // Right boundary if (bc.rCode == FLAT || bc.rCode == NATURAL) bc.rVal = 0.0; if (bc.rCode == FLAT || bc.rCode == DERIV1) { get_NUBasis_dfuncs_si (basis, M-1, bfuncs, abcd_right); abcd_right[3] = bc.rVal; } if (bc.rCode == NATURAL || bc.rCode == DERIV2) { get_NUBasis_d2funcs_si (basis, M-1, bfuncs, dbfuncs, abcd_right); abcd_right[3] = bc.rVal; } // Now, solve for coefficients solve_NUB_deriv_interp_1d_s (basis, data, dstride, coefs, cstride, abcd_left, abcd_right); } } NUBspline_1d_s * create_NUBspline_1d_s (NUgrid* x_grid, BCtype_s xBC, float *data) { // First, create the spline structure NUBspline_1d_s* spline = new NUBspline_1d_s; if (spline == NULL) return spline; spline->sp_code = NU1D; spline->t_code = SINGLE_REAL; // Next, create the basis spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); // M is the number of data points (but is unused) // int M; // if (xBC.lCode == PERIODIC) M = x_grid->num_points - 1; // else M = x_grid->num_points; int N = x_grid->num_points + 2; // Allocate coefficients and solve spline->coefs = (float*)malloc (sizeof(float)*N); find_NUBcoefs_1d_s (spline->x_basis, xBC, data, 1, spline->coefs, 1); return spline; } NUBspline_2d_s * create_NUBspline_2d_s (NUgrid* x_grid, NUgrid* y_grid, BCtype_s xBC, BCtype_s yBC, float *data) { // First, create the spline structure NUBspline_2d_s* spline = new NUBspline_2d_s; if (spline == NULL) return spline; spline->sp_code = NU2D; spline->t_code = SINGLE_REAL; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); // set but unused //int Mx, int My, Nx, Ny; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (float*)malloc (sizeof(float)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(float)*Nx*Ny); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } NUBspline_3d_s * create_NUBspline_3d_s (NUgrid* x_grid, NUgrid* y_grid, NUgrid* z_grid, BCtype_s xBC, BCtype_s yBC, BCtype_s zBC, float *data) { // First, create the spline structure NUBspline_3d_s* spline = new NUBspline_3d_s; if (spline == NULL) return spline; spline->sp_code = NU3D; spline->t_code = SINGLE_REAL; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); spline->z_basis = create_NUBasis (z_grid, zBC.lCode==PERIODIC); // set but unused // int Mx, int My, Mz, Nx, Ny, Nz; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; if (zBC.lCode == PERIODIC) Mz = z_grid->num_points - 1; else Mz = z_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; Nz = z_grid->num_points + 2; // Allocate coefficients and solve spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = (float*)malloc (sizeof(float)*Nx*Ny*Nz); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(float)*Nx*Ny*Nz); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_basis, zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //// Double-precision real creation routines //// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// void solve_NUB_deriv_interp_1d_d (NUBasis* restrict basis, double* restrict data, int datastride, double* restrict p, int pstride, double abcdInitial[4], double abcdFinal[4]) { int M = basis->grid->num_points; int N = M+2; // Banded matrix storage. The first three elements in the // tinyvector store the tridiagonal coefficients. The last element // stores the RHS data. #ifdef HAVE_C_VARARRAYS double bands[4*N]; #else double *bands = new double[4*N]; #endif // Fill up bands for (int i=0; i<4; i++) { bands[i] = abcdInitial[i]; bands[4*(N-1)+i] = abcdFinal[i]; } for (int i=0; i0; row--) p[pstride*(row)] = bands[4*(row)+3] - bands[4*(row)+2]*p[pstride*(row+1)]; // Finish with first row p[0] = bands[4*(0)+3] - bands[4*(0)+1]*p[pstride*1] - bands[4*(0)+2]*p[pstride*2]; #ifndef HAVE_C_VARARRAYS delete[] bands; #endif } void solve_NUB_periodic_interp_1d_d (NUBasis* restrict basis, double* restrict data, int datastride, double* restrict p, int pstride) { int M = basis->grid->num_points-1; // Banded matrix storage. The first three elements in the // tinyvector store the tridiagonal coefficients. The last element // stores the RHS data. #ifdef HAVE_C_VARARRAYS double bands[4*M], lastCol[M]; #else double *bands = new double[4*M]; double *lastCol = new double[ M]; #endif // Fill up bands for (int i=0; i=0; row--) p[pstride*(row+1)] = bands[4*(row)+3] - bands[4*(row)+2]*p[pstride*(row+2)] - lastCol[row]*p[pstride*M]; p[pstride* 0 ] = p[pstride*M]; p[pstride*(M+1)] = p[pstride*1]; p[pstride*(M+2)] = p[pstride*2]; #ifndef HAVE_C_VARARRAYS delete[] bands; delete[] lastCol; #endif } void find_NUBcoefs_1d_d (NUBasis* restrict basis, BCtype_d bc, double *data, int dstride, double *coefs, int cstride) { if (bc.lCode == PERIODIC) solve_NUB_periodic_interp_1d_d (basis, data, dstride, coefs, cstride); else { int M = basis->grid->num_points; // Setup boundary conditions - double bfuncs[4], dbfuncs[4], abcd_left[4], abcd_right[4]; + double bfuncs[4] {}; + double dbfuncs[4] {}; + double abcd_left[4] {}; + double abcd_right[4] {}; + // Left boundary if (bc.lCode == FLAT || bc.lCode == NATURAL) bc.lVal = 0.0; if (bc.lCode == FLAT || bc.lCode == DERIV1) { get_NUBasis_dfuncs_di (basis, 0, bfuncs, abcd_left); abcd_left[3] = bc.lVal; } if (bc.lCode == NATURAL || bc.lCode == DERIV2) { get_NUBasis_d2funcs_di (basis, 0, bfuncs, dbfuncs, abcd_left); abcd_left[3] = bc.lVal; } // Right boundary if (bc.rCode == FLAT || bc.rCode == NATURAL) bc.rVal = 0.0; if (bc.rCode == FLAT || bc.rCode == DERIV1) { get_NUBasis_dfuncs_di (basis, M-1, bfuncs, abcd_right); abcd_right[3] = bc.rVal; } if (bc.rCode == NATURAL || bc.rCode == DERIV2) { get_NUBasis_d2funcs_di (basis, M-1, bfuncs, dbfuncs, abcd_right); abcd_right[3] = bc.rVal; } // Now, solve for coefficients solve_NUB_deriv_interp_1d_d (basis, data, dstride, coefs, cstride, abcd_left, abcd_right); } } NUBspline_1d_d * create_NUBspline_1d_d (NUgrid* x_grid, BCtype_d xBC, double *data) { // First, create the spline structure NUBspline_1d_d* spline = new NUBspline_1d_d; if (spline == NULL) return spline; spline->sp_code = NU1D; spline->t_code = DOUBLE_REAL; // Next, create the basis spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); // M is the number of data points (set but unused) // int M; // if (xBC.lCode == PERIODIC) M = x_grid->num_points - 1; // else M = x_grid->num_points; int N = x_grid->num_points + 2; // Allocate coefficients and solve spline->coefs = (double*)malloc (sizeof(double)*N); find_NUBcoefs_1d_d (spline->x_basis, xBC, data, 1, spline->coefs, 1); return spline; } NUBspline_2d_d * create_NUBspline_2d_d (NUgrid* x_grid, NUgrid* y_grid, BCtype_d xBC, BCtype_d yBC, double *data) { // First, create the spline structure NUBspline_2d_d* spline = new NUBspline_2d_d; if (spline == NULL) return spline; spline->sp_code = NU2D; spline->t_code = DOUBLE_REAL; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); // int Mx, (set but unused) int My, Nx, Ny; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (double*)malloc (sizeof(double)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(double)*Nx*Ny); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } NUBspline_3d_d * create_NUBspline_3d_d (NUgrid* x_grid, NUgrid* y_grid, NUgrid* z_grid, BCtype_d xBC, BCtype_d yBC, BCtype_d zBC, double *data) { // First, create the spline structure NUBspline_3d_d* spline = new NUBspline_3d_d; if (spline == NULL) return spline; spline->sp_code = NU3D; spline->t_code = DOUBLE_REAL; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); spline->z_basis = create_NUBasis (z_grid, zBC.lCode==PERIODIC); // set but unused //int Mx, int My, Mz, Nx, Ny, Nz; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; if (zBC.lCode == PERIODIC) Mz = z_grid->num_points - 1; else Mz = z_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; Nz = z_grid->num_points + 2; spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = (double*)malloc (sizeof(double)*Nx*Ny*Nz); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(double)*Nx*Ny*Nz); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_basis, zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //// Single-precision complex creation routines //// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// void find_NUBcoefs_1d_c (NUBasis* restrict basis, BCtype_c bc, complex_float *data, int dstride, complex_float *coefs, int cstride) { BCtype_s bc_r, bc_i; bc_r.lCode = bc.lCode; bc_i.lCode = bc.lCode; bc_r.rCode = bc.rCode; bc_i.rCode = bc.rCode; bc_r.lVal = bc.lVal_r; bc_r.rVal = bc.rVal_r; bc_i.lVal = bc.lVal_i; bc_i.rVal = bc.rVal_i; float *data_r = ((float*)data ); float *data_i = ((float*)data )+1; float *coefs_r = ((float*)coefs); float *coefs_i = ((float*)coefs)+1; find_NUBcoefs_1d_s (basis, bc_r, data_r, 2*dstride, coefs_r, 2*cstride); find_NUBcoefs_1d_s (basis, bc_i, data_i, 2*dstride, coefs_i, 2*cstride); } NUBspline_1d_c * create_NUBspline_1d_c (NUgrid* x_grid, BCtype_c xBC, complex_float *data) { // First, create the spline structure NUBspline_1d_c* spline = new NUBspline_1d_c; if (spline == NULL) return spline; spline->sp_code = NU1D; spline->t_code = SINGLE_COMPLEX; // Next, create the basis spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); // M is the number of data points // int M; // if (xBC.lCode == PERIODIC) M = x_grid->num_points - 1; // else M = x_grid->num_points; int N = x_grid->num_points + 2; // Allocate coefficients and solve spline->coefs = (complex_float*)malloc (sizeof(complex_float)*N); find_NUBcoefs_1d_c (spline->x_basis, xBC, data, 1, spline->coefs, 1); return spline; } NUBspline_2d_c * create_NUBspline_2d_c (NUgrid* x_grid, NUgrid* y_grid, BCtype_c xBC, BCtype_c yBC, complex_float *data) { // First, create the spline structure NUBspline_2d_c* spline = new NUBspline_2d_c; if (spline == NULL) return spline; spline->sp_code = NU2D; spline->t_code = SINGLE_COMPLEX; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); // int Mx, int My, Nx, Ny; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (complex_float*)malloc (sizeof(complex_float)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(complex_float)*Nx*Ny); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } NUBspline_3d_c * create_NUBspline_3d_c (NUgrid* x_grid, NUgrid* y_grid, NUgrid* z_grid, BCtype_c xBC, BCtype_c yBC, BCtype_c zBC, complex_float *data) { // First, create the spline structure NUBspline_3d_c* spline = new NUBspline_3d_c; if (spline == NULL) return spline; spline->sp_code = NU3D; spline->t_code = SINGLE_COMPLEX; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); spline->z_basis = create_NUBasis (z_grid, zBC.lCode==PERIODIC); // int Mx, int My, Mz, Nx, Ny, Nz; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; if (zBC.lCode == PERIODIC) Mz = z_grid->num_points - 1; else Mz = z_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; Nz = z_grid->num_points + 2; // Allocate coefficients and solve spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = (complex_float*)malloc (sizeof(complex_float)*Nx*Ny*Nz); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(complex_float)*Nx*Ny*Nz); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_basis, zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //// Double-precision complex creation routines //// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// void find_NUBcoefs_1d_z (NUBasis* restrict basis, BCtype_z bc, complex_double *data, int dstride, complex_double *coefs, int cstride) { BCtype_d bc_r, bc_i; bc_r.lCode = bc.lCode; bc_i.lCode = bc.lCode; bc_r.rCode = bc.rCode; bc_i.rCode = bc.rCode; bc_r.lVal = bc.lVal_r; bc_r.rVal = bc.rVal_r; bc_i.lVal = bc.lVal_i; bc_i.rVal = bc.rVal_i; double *data_r = ((double*)data ); double *data_i = ((double*)data )+1; double *coefs_r = ((double*)coefs); double *coefs_i = ((double*)coefs)+1; find_NUBcoefs_1d_d (basis, bc_r, data_r, 2*dstride, coefs_r, 2*cstride); find_NUBcoefs_1d_d (basis, bc_i, data_i, 2*dstride, coefs_i, 2*cstride); } NUBspline_1d_z * create_NUBspline_1d_z (NUgrid* x_grid, BCtype_z xBC, complex_double *data) { // First, create the spline structure NUBspline_1d_z* spline = new NUBspline_1d_z; if (spline == NULL) return spline; spline->sp_code = NU1D; spline->t_code = DOUBLE_COMPLEX; // Next, create the basis spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); // M is the number of data points // int M; // if (xBC.lCode == PERIODIC) M = x_grid->num_points - 1; // else M = x_grid->num_points; int N = x_grid->num_points + 2; // Allocate coefficients and solve spline->coefs = (complex_double*)malloc (sizeof(complex_double)*N); find_NUBcoefs_1d_z (spline->x_basis, xBC, data, 1, spline->coefs, 1); return spline; } NUBspline_2d_z * create_NUBspline_2d_z (NUgrid* x_grid, NUgrid* y_grid, BCtype_z xBC, BCtype_z yBC, complex_double *data) { // First, create the spline structure NUBspline_2d_z* spline = new NUBspline_2d_z; if (spline == NULL) return spline; spline->sp_code = NU2D; spline->t_code = DOUBLE_COMPLEX; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); // int Mx, int My, Nx, Ny; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (complex_double*)malloc (sizeof(complex_double)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(complex_double)*Nx*Ny); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } NUBspline_3d_z * create_NUBspline_3d_z (NUgrid* x_grid, NUgrid* y_grid, NUgrid* z_grid, BCtype_z xBC, BCtype_z yBC, BCtype_z zBC, complex_double *data) { // First, create the spline structure NUBspline_3d_z* spline = new NUBspline_3d_z; if (spline == NULL) return spline; spline->sp_code = NU3D; spline->t_code = DOUBLE_COMPLEX; spline->x_grid = x_grid; spline->y_grid = y_grid; spline->z_grid = z_grid; // Next, create the bases spline->x_basis = create_NUBasis (x_grid, xBC.lCode==PERIODIC); spline->y_basis = create_NUBasis (y_grid, yBC.lCode==PERIODIC); spline->z_basis = create_NUBasis (z_grid, zBC.lCode==PERIODIC); // int Mx, int My, Mz, Nx, Ny, Nz; // if (xBC.lCode == PERIODIC) Mx = x_grid->num_points - 1; // else Mx = x_grid->num_points; if (yBC.lCode == PERIODIC) My = y_grid->num_points - 1; else My = y_grid->num_points; if (zBC.lCode == PERIODIC) Mz = z_grid->num_points - 1; else Mz = z_grid->num_points; Nx = x_grid->num_points + 2; Ny = y_grid->num_points + 2; Nz = z_grid->num_points + 2; // Allocate coefficients and solve spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = (complex_double*)malloc (sizeof(complex_double)*Nx*Ny*Nz); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(complex_double)*Nx*Ny*Nz); #endif // First, solve in the X-direction for (int iy=0; iyx_basis, xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); /* for (int ix=0; ixcoefs[coffset+ix*spline->x_stride]; if (isnan(creal(z))) fprintf (stderr, "NAN encountered in create_NUBspline_3d_z at real part of (%d,%d,%d)\n", ix,iy,iz); if (isnan(cimag(z))) fprintf (stderr, "NAN encountered in create_NUBspline_3d_z at imag part of (%d,%d,%d)\n", ix,iy,iz); } */ } // Now, solve in the Y-direction for (int ix=0; ixy_basis, yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_basis, zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } return spline; } void destroy_NUBspline(Bspline *spline) { free(spline->coefs); switch (spline->sp_code) { case NU1D: destroy_NUBasis (((NUBspline_1d*)spline)->x_basis); break; case NU2D: destroy_NUBasis (((NUBspline_2d*)spline)->x_basis); destroy_NUBasis (((NUBspline_2d*)spline)->y_basis); break; case NU3D: destroy_NUBasis (((NUBspline_3d*)spline)->x_basis); destroy_NUBasis (((NUBspline_3d*)spline)->y_basis); destroy_NUBasis (((NUBspline_3d*)spline)->z_basis); break; case U1D: case U2D: case MULTI_U1D: case MULTI_U2D: case MULTI_U3D: case MULTI_NU1D: case MULTI_NU2D: case MULTI_NU3D: default: ; } delete spline; } diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp index ce3b06a39d..3127c5b281 100644 --- a/libs/image/brushengine/kis_paintop_settings.cpp +++ b/libs/image/brushengine/kis_paintop_settings.cpp @@ -1,510 +1,512 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Lukáš Tvrdý * Copyright (c) 2014 Mohit Goyal * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paintop_registry.h" #include "kis_timing_information.h" #include #include "kis_paintop_config_widget.h" #include #include "kis_paintop_settings_update_proxy.h" #include #include #include #include #include #include "KisPaintopSettingsIds.h" struct Q_DECL_HIDDEN KisPaintOpSettings::Private { Private() : disableDirtyNotifications(false) {} QPointer settingsWidget; QString modelName; KisPaintOpPresetWSP preset; QList uniformProperties; bool disableDirtyNotifications; class DirtyNotificationsLocker { public: DirtyNotificationsLocker(KisPaintOpSettings::Private *d) : m_d(d), m_oldNotificationsState(d->disableDirtyNotifications) { m_d->disableDirtyNotifications = true; } ~DirtyNotificationsLocker() { m_d->disableDirtyNotifications = m_oldNotificationsState; } private: KisPaintOpSettings::Private *m_d; bool m_oldNotificationsState; Q_DISABLE_COPY(DirtyNotificationsLocker) }; KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxyNoCreate() : 0; } KisPaintopSettingsUpdateProxy* updateProxyCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxy() : 0; } }; KisPaintOpSettings::KisPaintOpSettings() : d(new Private) { d->preset = 0; } KisPaintOpSettings::~KisPaintOpSettings() { } KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs) : KisPropertiesConfiguration(rhs) , d(new Private) { d->settingsWidget = 0; d->preset = rhs.preset(); d->modelName = rhs.modelName(); } void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget) { d->settingsWidget = widget; } void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset) { d->preset = preset; } KisPaintOpPresetWSP KisPaintOpSettings::preset() const { return d->preset; } bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { Q_UNUSED(modifiers); Q_UNUSED(currentNode); setRandomOffset(paintInformation); return true; // ignore the event by default } bool KisPaintOpSettings::mouseReleaseEvent() { return true; // ignore the event by default } void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation) { + bool disableDirtyBefore = d->disableDirtyNotifications; + d->disableDirtyNotifications = true; if (getBool("Texture/Pattern/Enabled")) { if (getBool("Texture/Pattern/isRandomOffsetX")) { setProperty("Texture/Pattern/OffsetX", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX"))); } if (getBool("Texture/Pattern/isRandomOffsetY")) { setProperty("Texture/Pattern/OffsetY", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY"))); } } - + d->disableDirtyNotifications = disableDirtyBefore; } bool KisPaintOpSettings::hasMaskingSettings() const { return getBool(KisPaintOpUtils::MaskingBrushEnabledTag, false); } KisPaintOpSettingsSP KisPaintOpSettings::createMaskingSettings() const { if (!hasMaskingSettings()) return KisPaintOpSettingsSP(); const KoID pixelBrushId(KisPaintOpUtils::MaskingBrushPaintOpId, QString()); KisPaintOpSettingsSP maskingSettings = KisPaintOpRegistry::instance()->settings(pixelBrushId); this->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSettings); const bool useMasterSize = this->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true); if (useMasterSize) { const qreal masterSizeCoeff = getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0); maskingSettings->setPaintOpSize(masterSizeCoeff * paintOpSize()); } return maskingSettings; } QString KisPaintOpSettings::maskingBrushCompositeOp() const { return getString(KisPaintOpUtils::MaskingBrushCompositeOpTag, COMPOSITE_MULT); } KisPaintOpSettingsSP KisPaintOpSettings::clone() const { QString paintopID = getString("paintop"); if (paintopID.isEmpty()) return 0; KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID)); QMapIterator i(getProperties()); while (i.hasNext()) { i.next(); settings->setProperty(i.key(), QVariant(i.value())); } settings->setPreset(this->preset()); return settings; } void KisPaintOpSettings::resetSettings(const QStringList &preserveProperties) { QStringList allKeys = preserveProperties; allKeys << "paintop"; QHash preserved; Q_FOREACH (const QString &key, allKeys) { if (hasProperty(key)) { preserved[key] = getProperty(key); } } clearProperties(); for (auto it = preserved.constBegin(); it != preserved.constEnd(); ++it) { setProperty(it.key(), it.value()); } } void KisPaintOpSettings::activate() { } void KisPaintOpSettings::setPaintOpOpacity(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("OpacityValue", value); } void KisPaintOpSettings::setPaintOpFlow(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("FlowValue", value); } void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("CompositeOp", value); } qreal KisPaintOpSettings::paintOpOpacity() { KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this); return proxy->getDouble("OpacityValue", 1.0); } qreal KisPaintOpSettings::paintOpFlow() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getDouble("FlowValue", 1.0); } QString KisPaintOpSettings::paintOpCompositeOp() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getString("CompositeOp", COMPOSITE_OVER); } void KisPaintOpSettings::setEraserMode(bool value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("EraserMode", value); } bool KisPaintOpSettings::eraserMode() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getBool("EraserMode", false); } QString KisPaintOpSettings::effectivePaintOpCompositeOp() { return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE; } qreal KisPaintOpSettings::savedEraserSize() const { return getDouble("SavedEraserSize", 0.0); } void KisPaintOpSettings::setSavedEraserSize(qreal value) { setProperty("SavedEraserSize", value); setPropertyNotSaved("SavedEraserSize"); } qreal KisPaintOpSettings::savedBrushSize() const { return getDouble("SavedBrushSize", 0.0); } void KisPaintOpSettings::setSavedBrushSize(qreal value) { setProperty("SavedBrushSize", value); setPropertyNotSaved("SavedBrushSize"); } qreal KisPaintOpSettings::savedEraserOpacity() const { return getDouble("SavedEraserOpacity", 0.0); } void KisPaintOpSettings::setSavedEraserOpacity(qreal value) { setProperty("SavedEraserOpacity", value); setPropertyNotSaved("SavedEraserOpacity"); } qreal KisPaintOpSettings::savedBrushOpacity() const { return getDouble("SavedBrushOpacity", 0.0); } void KisPaintOpSettings::setSavedBrushOpacity(qreal value) { setProperty("SavedBrushOpacity", value); setPropertyNotSaved("SavedBrushOpacity"); } QString KisPaintOpSettings::modelName() const { return d->modelName; } void KisPaintOpSettings::setModelName(const QString & modelName) { d->modelName = modelName; } KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const { if (d->settingsWidget.isNull()) return 0; return d->settingsWidget.data(); } bool KisPaintOpSettings::isValid() const { return true; } bool KisPaintOpSettings::isLoadable() { return isValid(); } QString KisPaintOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_ALPHA_DARKEN; } bool KisPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED, false); } qreal KisPaintOpSettings::airbrushInterval() const { qreal rate = getDouble(AIRBRUSH_RATE, 1.0); if (rate == 0.0) { return LONG_TIME; } else { return 1000.0 / rate; } } bool KisPaintOpSettings::useSpacingUpdates() const { return getBool(SPACING_USE_UPDATES, false); } bool KisPaintOpSettings::needsAsynchronousUpdates() const { return false; } QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; if (mode.isVisible) { path = ellipseOutline(10, 10, 1.0, 0); if (mode.showTiltDecoration) { path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0)); } path.translate(info.pos()); } return path; } QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) { QPainterPath path; QRectF ellipse(0, 0, width * scale, height * scale); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); QTransform m; m.reset(); m.rotate(rotation); path = m.map(path); return path; } QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal maxLength, qreal angle) { if (maxLength == 0.0) maxLength = 50.0; maxLength = qMax(maxLength, 50.0); qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true)); qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0); QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle); guideLine.translate(start); QPainterPath ret; ret.moveTo(guideLine.p1()); ret.lineTo(guideLine.p2()); guideLine.setAngle(baseAngle - angle); ret.lineTo(guideLine.p2()); ret.lineTo(guideLine.p1()); return ret; } void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value) { if (value != KisPropertiesConfiguration::getProperty(name) && !d->disableDirtyNotifications) { KisPaintOpPresetSP presetSP = preset().toStrongRef(); if (presetSP) { presetSP->setDirty(true); } } KisPropertiesConfiguration::setProperty(name, value); onPropertyChanged(); } void KisPaintOpSettings::onPropertyChanged() { KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate(); if (proxy) { proxy->notifySettingsChanged(); } } bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config) { return config->getBool("lodUserAllowed", true); } void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value) { config->setProperty("lodUserAllowed", value); } bool KisPaintOpSettings::lodSizeThresholdSupported() const { return true; } qreal KisPaintOpSettings::lodSizeThreshold() const { return getDouble("lodSizeThreshold", 100.0); } void KisPaintOpSettings::setLodSizeThreshold(qreal value) { setProperty("lodSizeThreshold", value); } #include "kis_standard_uniform_properties_factory.h" QList KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(d->uniformProperties); if (props.isEmpty()) { using namespace KisStandardUniformPropertiesFactory; props.append(createProperty(opacity, settings, d->updateProxyCreate())); props.append(createProperty(size, settings, d->updateProxyCreate())); props.append(createProperty(flow, settings, d->updateProxyCreate())); d->uniformProperties = listStrongToWeak(props); } return props; } diff --git a/libs/image/bsplines/kis_bspline_1d.cpp b/libs/image/bsplines/kis_bspline_1d.cpp index 3ae735f8f7..9733283d46 100644 --- a/libs/image/bsplines/kis_bspline_1d.cpp +++ b/libs/image/bsplines/kis_bspline_1d.cpp @@ -1,93 +1,98 @@ /* * 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_bspline_1d.h" #include #include #include "einspline/bspline_create.h" #include "einspline/bspline_eval_std_s.h" #include "kis_bspline_p.h" namespace KisBSplines { struct Q_DECL_HIDDEN KisBSpline1D::Private { BorderCondition bc; UBspline_1d_s* spline; }; KisBSpline1D::KisBSpline1D(float gridStart, float gridEnd, int numSamples, BorderCondition bc) : m_d(new Private) { m_gridStart = gridStart; m_gridEnd = gridEnd/* + step*/; m_numSamples = numSamples/* + 1*/; m_d->bc = bc; m_d->spline = 0; } KisBSpline1D::~KisBSpline1D() { if (m_d->spline) { destroy_Bspline(m_d->spline); } } void KisBSpline1D::initializeSplineImpl(const QVector &values) { Ugrid grid; grid.start = m_gridStart; grid.end = m_gridEnd; grid.num = m_numSamples; + grid.delta = 0.0; + grid.delta_inv = 0.0; BCtype_s bctype; bctype.lCode = bctype.rCode = convertBorderType(m_d->bc); + bctype.lVal = 0.0; + bctype.rVal = 0.0; + m_d->spline = create_UBspline_1d_s(grid, bctype, const_cast(values.constData())); } float KisBSpline1D::value(float x) const { /** * The spline works for an open interval only, so include the last point * explicitly */ if (x == m_gridEnd) { x -= x * std::numeric_limits::epsilon(); } KIS_ASSERT_RECOVER_NOOP(x >= m_gridStart && x < m_gridEnd); float value; eval_UBspline_1d_s (m_d->spline, x, &value); return value; } } diff --git a/libs/image/bsplines/kis_bspline_2d.cpp b/libs/image/bsplines/kis_bspline_2d.cpp index 44b3e5edcd..481f523e09 100644 --- a/libs/image/bsplines/kis_bspline_2d.cpp +++ b/libs/image/bsplines/kis_bspline_2d.cpp @@ -1,122 +1,130 @@ /* * 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_bspline_2d.h" #include #include #include "einspline/bspline_create.h" #include "einspline/bspline_eval_std_s.h" #include "kis_bspline_p.h" namespace KisBSplines { struct Q_DECL_HIDDEN KisBSpline2D::Private { BorderCondition bcX; BorderCondition bcY; UBspline_2d_s* spline; }; KisBSpline2D::KisBSpline2D(float xStart, float xEnd, int numSamplesX, BorderCondition bcX, float yStart, float yEnd, int numSamplesY, BorderCondition bcY) : m_d(new Private) { m_xStart = xStart; m_xEnd = xEnd; m_numSamplesX = numSamplesX; m_yStart = yStart; m_yEnd = yEnd; m_numSamplesY = numSamplesY; m_d->bcX = bcX; m_d->bcY = bcY; m_d->spline = 0; } KisBSpline2D::~KisBSpline2D() { if (m_d->spline) { destroy_Bspline(m_d->spline); } } void KisBSpline2D::initializeSplineImpl(const QVector &values) { Ugrid xGrid; xGrid.start = m_xStart; xGrid.end = m_xEnd; xGrid.num = m_numSamplesX; + xGrid.delta = 0.0; + xGrid.delta_inv = 0.0; Ugrid yGrid; yGrid.start = m_yStart; yGrid.end = m_yEnd; yGrid.num = m_numSamplesY; + yGrid.delta = 0.0; + yGrid.delta_inv = 0.0; BCtype_s bctypeX; bctypeX.lCode = bctypeX.rCode = convertBorderType(m_d->bcX); + bctypeX.lVal = 0.0; + bctypeX.rVal = 0.0; BCtype_s bctypeY; bctypeY.lCode = bctypeY.rCode = convertBorderType(m_d->bcY); + bctypeY.lVal = 0.0; + bctypeY.rVal = 0.0; m_d->spline = create_UBspline_2d_s(xGrid, yGrid, bctypeX, bctypeY, const_cast(values.constData())); } float KisBSpline2D::value(float x, float y) const { /** * The spline works for an open interval only, so include the last point * explicitly */ if (x == m_xEnd) { x -= x * std::numeric_limits::epsilon(); } if (y == m_yEnd) { y -= y * std::numeric_limits::epsilon(); } KIS_ASSERT_RECOVER_NOOP(x >= m_xStart && x < m_xEnd); KIS_ASSERT_RECOVER_NOOP(y >= m_yStart && y < m_yEnd); float value; eval_UBspline_2d_s (m_d->spline, x, y, &value); return value; } BorderCondition KisBSpline2D::borderConditionX() const { return m_d->bcX; } BorderCondition KisBSpline2D::borderConditionY() const { return m_d->bcY; } } diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index 750d827c09..26ce86a28f 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,732 +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; + const quint8 *m_data {0}; 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; 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 &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 &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_node.cpp b/libs/image/kis_base_node.cpp index 653cc0ccea..5ae959f51a 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,465 +1,465 @@ /* * 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_base_node.h" #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_layer_properties_icons.h" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; KisBaseNode::Property hack_visible; //HACK QUuid id; QMap keyframeChannels; QScopedPointer opacityChannel; bool systemLocked; bool collapsed; bool supportsLodMoves; bool animated; bool useInTimeline; KisImageWSP image; Private(KisImageWSP image) : id(QUuid::createUuid()) , systemLocked(false) , collapsed(false) , supportsLodMoves(false) , animated(false) - , useInTimeline(false) + , useInTimeline(true) , image(image) { } Private(const Private &rhs) : compositeOp(rhs.compositeOp), id(QUuid::createUuid()), systemLocked(false), collapsed(rhs.collapsed), supportsLodMoves(rhs.supportsLodMoves), animated(rhs.animated), useInTimeline(rhs.useInTimeline), image(rhs.image) { QMapIterator iter = rhs.properties.propertyIterator(); while (iter.hasNext()) { iter.next(); properties.setProperty(iter.key(), iter.value()); } } }; KisBaseNode::KisBaseNode(KisImageWSP image) : m_d(new Private(image)) { /** * Be cautious! These two calls are vital to warm-up KoProperties. * We use it and its QMap in a threaded environment. This is not * officially supported by Qt, but our environment guarantees, that * there will be the only writer and several readers. Whilst the * value of the QMap is boolean and there are no implicit-sharing * calls provocated, it is safe to work with it in such an * environment. */ setVisible(true, true); setUserLocked(false); setCollapsed(false); setSupportsLodMoves(true); m_d->compositeOp = COMPOSITE_OVER; } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private(*rhs.m_d)) { if (rhs.m_d->keyframeChannels.size() > 0) { Q_FOREACH(QString key, rhs.m_d->keyframeChannels.keys()) { KisKeyframeChannel* channel = rhs.m_d->keyframeChannels.value(key); if (!channel) { continue; } if (channel->inherits("KisScalarKeyframeChannel")) { KisScalarKeyframeChannel* pchannel = qobject_cast(channel); KIS_ASSERT_RECOVER(pchannel) { continue; } KisScalarKeyframeChannel* channelNew = new KisScalarKeyframeChannel(*pchannel, 0); KIS_ASSERT(channelNew); m_d->keyframeChannels.insert(channelNew->id(), channelNew); if (KoID(key) == KisKeyframeChannel::Opacity) { m_d->opacityChannel.reset(channelNew); } } } } } KisBaseNode::~KisBaseNode() { delete m_d; } KisPaintDeviceSP KisBaseNode::colorPickSourceDevice() const { return projection(); } quint8 KisBaseNode::opacity() const { if (m_d->opacityChannel) { qreal value = m_d->opacityChannel->currentValue(); if (!qIsNaN(value)) { return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, val); } } if (opacity() == val) return; setNodeProperty("opacity", val); baseNodeInvalidateAllFramesCallback(); } quint8 KisBaseNode::percentOpacity() const { return int(float(opacity() * 100) / 255 + 0.5); } void KisBaseNode::setPercentOpacity(quint8 val) { setOpacity(int(float(val * 255) / 100 + 0.5)); } const QString& KisBaseNode::compositeOpId() const { return m_d->compositeOp; } void KisBaseNode::setCompositeOpId(const QString& compositeOp) { if (m_d->compositeOp == compositeOp) return; m_d->compositeOp = compositeOp; baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const { KisBaseNode::PropertyList l; l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked()); return l; } void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { setVisible(properties.at(0).state.toBool()); m_d->hack_visible = properties.at(0); setUserLocked(properties.at(1).state.toBool()); } const KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value) { m_d->properties.setProperty(name, value); baseNodeChangedCallback(); } void KisBaseNode::mergeNodeProperties(const KoProperties & properties) { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } bool KisBaseNode::check(const KoProperties & properties) const { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); if (m_d->properties.contains(iter.key())) { if (m_d->properties.value(iter.key()) != iter.value()) return false; } } return true; } QImage KisBaseNode::createThumbnail(qint32 w, qint32 h) { try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (const std::bad_alloc&) { return QImage(); } } QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time) { Q_UNUSED(time) return createThumbnail(w, h); } bool KisBaseNode::visible(bool recursive) const { bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); KisBaseNodeSP parentNode = parentCallback(); return recursive && isVisible && parentNode ? parentNode->visible(recursive) : isVisible; } void KisBaseNode::setVisible(bool visible, bool loading) { const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); if (!loading && isVisible == visible) return; m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible); notifyParentVisibilityChanged(visible); if (!loading) { baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } void KisBaseNode::setUserLocked(bool locked) { const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true); if (isLocked == locked) return; m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked); baseNodeChangedCallback(); } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = (visible(false) && !userLocked()); } else { editable = (!userLocked()); } if (editable) { KisBaseNodeSP parentNode = parentCallback(); if (parentNode && parentNode != this) { editable = parentNode->isEditable(checkVisibility); } } return editable; } bool KisBaseNode::hasEditablePaintDevice() const { return paintDevice() && isEditable(); } void KisBaseNode::setCollapsed(bool collapsed) { m_d->collapsed = collapsed; } bool KisBaseNode::collapsed() const { return m_d->collapsed; } void KisBaseNode::setColorLabelIndex(int index) { const int currentLabel = colorLabelIndex(); if (currentLabel == index) return; m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index); baseNodeChangedCallback(); } int KisBaseNode::colorLabelIndex() const { return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0); } QUuid KisBaseNode::uuid() const { return m_d->id; } void KisBaseNode::setUuid(const QUuid& id) { m_d->id = id; baseNodeChangedCallback(); } bool KisBaseNode::supportsLodMoves() const { return m_d->supportsLodMoves; } void KisBaseNode::setImage(KisImageWSP image) { m_d->image = image; } KisImageWSP KisBaseNode::image() const { return m_d->image; } bool KisBaseNode::isFakeNode() const { return false; } void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } QMap KisBaseNode::keyframeChannels() const { return m_d->keyframeChannels; } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } bool KisBaseNode::useInTimeline() const { return m_d->useInTimeline; } void KisBaseNode::setUseInTimeline(bool value) { if (value == m_d->useInTimeline) return; m_d->useInTimeline = value; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); emit keyframeChannelAdded(channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, device->defaultBounds(), KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_scalar_keyframe_channel.cpp b/libs/image/kis_scalar_keyframe_channel.cpp index ba73be65d7..2cc74ea0e0 100644 --- a/libs/image/kis_scalar_keyframe_channel.cpp +++ b/libs/image/kis_scalar_keyframe_channel.cpp @@ -1,487 +1,489 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_scalar_keyframe_channel.h" #include "kis_node.h" #include "kundo2command.h" #include "kis_time_range.h" #include #include struct KisScalarKeyframe : public KisKeyframe { KisScalarKeyframe(KisKeyframeChannel *channel, int time, qreal value) : KisKeyframe(channel, time) , value(value) {} KisScalarKeyframe(const KisScalarKeyframe *rhs, KisKeyframeChannel *channel) : KisKeyframe(rhs, channel) , value(rhs->value) {} qreal value; KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override { return toQShared(new KisScalarKeyframe(this, channel)); } }; KisScalarKeyframeChannel::AddKeyframeCommand::AddKeyframeCommand(KisScalarKeyframeChannel *channel, int time, qreal value, KUndo2Command *parentCommand) : KisReplaceKeyframeCommand(channel, time, channel->createKeyframe(time, value, parentCommand), parentCommand) {} struct KisScalarKeyframeChannel::Private { public: Private(qreal min, qreal max, KisKeyframe::InterpolationMode defaultInterpolation) : minValue(min), maxValue(max), firstFreeIndex(0), defaultInterpolation(defaultInterpolation) {} Private(const Private &rhs) : minValue(rhs.minValue), maxValue(rhs.maxValue), firstFreeIndex(rhs.firstFreeIndex), defaultInterpolation(rhs.defaultInterpolation) {} qreal minValue; qreal maxValue; int firstFreeIndex; KisKeyframe::InterpolationMode defaultInterpolation; struct SetValueCommand; struct SetTangentsCommand; struct SetInterpolationModeCommand; }; KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KoID &id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation) : KisKeyframeChannel(id, defaultBounds), m_d(new Private(minValue, maxValue, defaultInterpolation)) { } KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KisScalarKeyframeChannel &rhs, KisNode *newParentNode) : KisKeyframeChannel(rhs, newParentNode), m_d(new Private(*rhs.m_d)) { } KisScalarKeyframeChannel::~KisScalarKeyframeChannel() {} bool KisScalarKeyframeChannel::hasScalarValue() const { return true; } qreal KisScalarKeyframeChannel::minScalarValue() const { return m_d->minValue; } qreal KisScalarKeyframeChannel::maxScalarValue() const { return m_d->maxValue; } qreal KisScalarKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { KisScalarKeyframe *key = dynamic_cast(keyframe.data()); Q_ASSERT(key != 0); return key->value; } struct KisScalarKeyframeChannel::Private::SetValueCommand : public KUndo2Command { SetValueCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, qreal oldValue, qreal newValue, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldValue(oldValue), m_newValue(newValue) { } void redo() override { setValue(m_newValue); } void undo() override { setValue(m_oldValue); } void setValue(qreal value) { KisScalarKeyframe *key = dynamic_cast(m_keyframe.data()); Q_ASSERT(key != 0); key->value = value; m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; qreal m_oldValue; qreal m_newValue; }; struct KisScalarKeyframeChannel::Private::SetTangentsCommand : public KUndo2Command { SetTangentsCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode oldMode, QPointF oldLeftTangent, QPointF oldRightTangent, KisKeyframe::InterpolationTangentsMode newMode, QPointF newLeftTangent, QPointF newRightTangent, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldMode(oldMode), m_oldLeftTangent(oldLeftTangent), m_oldRightTangent(oldRightTangent), m_newMode(newMode), m_newLeftTangent(newLeftTangent), m_newRightTangent(newRightTangent) { } void redo() override { m_keyframe->setTangentsMode(m_newMode); m_keyframe->setInterpolationTangents(m_newLeftTangent, m_newRightTangent); m_channel->notifyKeyframeChanged(m_keyframe); } void undo() override { m_keyframe->setTangentsMode(m_oldMode); m_keyframe->setInterpolationTangents(m_oldLeftTangent, m_oldRightTangent); m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; KisKeyframe::InterpolationTangentsMode m_oldMode; QPointF m_oldLeftTangent; QPointF m_oldRightTangent; KisKeyframe::InterpolationTangentsMode m_newMode; QPointF m_newLeftTangent; QPointF m_newRightTangent; }; struct KisScalarKeyframeChannel::Private::SetInterpolationModeCommand : public KUndo2Command { SetInterpolationModeCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, KisKeyframe::InterpolationMode oldMode, KisKeyframe::InterpolationMode newMode, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldMode(oldMode), m_newMode(newMode) { } void redo() override { m_keyframe->setInterpolationMode(m_newMode); m_channel->notifyKeyframeChanged(m_keyframe); } void undo() override { m_keyframe->setInterpolationMode(m_oldMode); m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; KisKeyframe::InterpolationMode m_oldMode; KisKeyframe::InterpolationMode m_newMode; }; void KisScalarKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } qreal oldValue = scalarValue(keyframe); KUndo2Command *cmd = new Private::SetValueCommand(this, keyframe, oldValue, value, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::setInterpolationMode(KisKeyframeSP keyframe, KisKeyframe::InterpolationMode mode, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } KisKeyframe::InterpolationMode oldMode = keyframe->interpolationMode(); KUndo2Command *cmd = new Private::SetInterpolationModeCommand(this, keyframe, oldMode, mode, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::setInterpolationTangents(KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode mode, QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } KisKeyframe::InterpolationTangentsMode oldMode = keyframe->tangentsMode(); QPointF oldLeftTangent = keyframe->leftTangent(); QPointF oldRightTangent = keyframe->rightTangent(); KUndo2Command *cmd = new Private::SetTangentsCommand(this, keyframe, oldMode, oldLeftTangent, oldRightTangent, mode, leftTangent, rightTangent, parentCommand); cmd->redo(); } qreal cubicBezier(qreal p0, qreal delta1, qreal delta2, qreal p3, qreal t) { qreal p1 = p0 + delta1; qreal p2 = p3 + delta2; qreal c = 1-t; return c*c*c * p0 + 3*c*c*t * p1 + 3*c*t*t * p2 + t*t*t * p3; } void normalizeTangents(const QPointF point1, QPointF &rightTangent, QPointF &leftTangent, const QPointF point2) { // To ensure that the curve is monotonic wrt time, // check that control points lie between the endpoints. // If not, force them into range by scaling down the tangents float interval = point2.x() - point1.x(); if (rightTangent.x() < 0) rightTangent *= 0; if (leftTangent.x() > 0) leftTangent *= 0; if (rightTangent.x() > interval) { rightTangent *= interval / rightTangent.x(); } if (leftTangent.x() < -interval) { leftTangent *= interval / -leftTangent.x(); } } QPointF KisScalarKeyframeChannel::interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t) { normalizeTangents(point1, rightTangent, leftTangent, point2); qreal x = cubicBezier(point1.x(), rightTangent.x(), leftTangent.x(), point2.x(), t); qreal y = cubicBezier(point1.y(), rightTangent.y(), leftTangent.y(), point2.y(), t); return QPointF(x,y); } qreal findCubicCurveParameter(int time0, qreal delta0, qreal delta1, int time1, int time) { if (time == time0) return 0.0; if (time == time1) return 1.0; qreal min_t = 0.0; qreal max_t = 1.0; while (true) { qreal t = (max_t + min_t) / 2; qreal time_t = cubicBezier(time0, delta0, delta1, time1, t); if (time_t < time - 0.05) { min_t = t; } else if (time_t > time + 0.05) { max_t = t; } else { // Close enough return t; } } } qreal KisScalarKeyframeChannel::interpolatedValue(int time) const { KisKeyframeSP activeKey = activeKeyframeAt(time); if (activeKey.isNull()) return qQNaN(); KisKeyframeSP nextKey = nextKeyframe(activeKey); qreal result = qQNaN(); if (time == activeKey->time() || nextKey.isNull()) { result = scalarValue(activeKey); } else { switch (activeKey->interpolationMode()) { case KisKeyframe::Constant: result = scalarValue(activeKey); break; case KisKeyframe::Linear: { int time0 = activeKey->time(); int time1 = nextKey->time(); qreal value0 = scalarValue(activeKey); qreal value1 = scalarValue(nextKey); result = value0 + (value1 - value0) * (time - time0) / (time1 - time0); } break; case KisKeyframe::Bezier: { QPointF point0 = QPointF(activeKey->time(), scalarValue(activeKey)); QPointF point1 = QPointF(nextKey->time(), scalarValue(nextKey)); QPointF tangent0 = activeKey->rightTangent(); QPointF tangent1 = nextKey->leftTangent(); normalizeTangents(point0, tangent0, tangent1, point1); qreal t = findCubicCurveParameter(point0.x(), tangent0.x(), tangent1.x(), point1.x(), time); result = interpolate(point0, tangent0, tangent1, point1, t).y(); } break; default: KIS_ASSERT_RECOVER_BREAK(false); break; } } if (result > m_d->maxValue) return m_d->maxValue; if (result < m_d->minValue) return m_d->minValue; return result; } qreal KisScalarKeyframeChannel::currentValue() const { return interpolatedValue(currentTime()); } KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { if (copySrc) { KisScalarKeyframe *srcKeyframe = dynamic_cast(copySrc.data()); Q_ASSERT(srcKeyframe); KisScalarKeyframe *keyframe = new KisScalarKeyframe(srcKeyframe, this); keyframe->setTime(time); return toQShared(keyframe); } else { return createKeyframe(time, 0, parentCommand); } } KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, qreal value, KUndo2Command *parentCommand) { Q_UNUSED(parentCommand); KisScalarKeyframe *keyframe = new KisScalarKeyframe(this, time, value); keyframe->setInterpolationMode(m_d->defaultInterpolation); return toQShared(keyframe); } void KisScalarKeyframeChannel::destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) { Q_UNUSED(parentCommand); Q_UNUSED(key); } void KisScalarKeyframeChannel::uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) { KisScalarKeyframeChannel *srcScalarChannel = dynamic_cast(srcChannel); KIS_ASSERT_RECOVER_RETURN(srcScalarChannel); KisKeyframeSP srcFrame = srcScalarChannel->keyframeAt(srcTime); KIS_ASSERT_RECOVER_RETURN(srcFrame); KisScalarKeyframe *dstKey = dynamic_cast(dstFrame.data()); - dstKey->value = srcChannel->scalarValue(srcFrame); - notifyKeyframeChanged(dstFrame); + if (dstKey) { + dstKey->value = srcChannel->scalarValue(srcFrame); + notifyKeyframeChanged(dstFrame); + } } QRect KisScalarKeyframeChannel::affectedRect(KisKeyframeSP key) { Q_UNUSED(key); if (node()) { return node()->extent(); } else { return QRect(); } } void KisScalarKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) { Q_UNUSED(layerFilename); keyframeElement.setAttribute("value", KisDomUtils::toString(scalarValue(keyframe))); QString interpolationMode; if (keyframe->interpolationMode() == KisKeyframe::Constant) interpolationMode = "constant"; if (keyframe->interpolationMode() == KisKeyframe::Linear) interpolationMode = "linear"; if (keyframe->interpolationMode() == KisKeyframe::Bezier) interpolationMode = "bezier"; QString tangentsMode; if (keyframe->tangentsMode() == KisKeyframe::Smooth) tangentsMode = "smooth"; if (keyframe->tangentsMode() == KisKeyframe::Sharp) tangentsMode = "sharp"; keyframeElement.setAttribute("interpolation", interpolationMode); keyframeElement.setAttribute("tangents", tangentsMode); KisDomUtils::saveValue(&keyframeElement, "leftTangent", keyframe->leftTangent()); KisDomUtils::saveValue(&keyframeElement, "rightTangent", keyframe->rightTangent()); } KisKeyframeSP KisScalarKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode) { int time = keyframeNode.toElement().attribute("time").toInt(); workaroundBrokenFrameTimeBug(&time); qreal value = KisDomUtils::toDouble(keyframeNode.toElement().attribute("value")); KUndo2Command tempParentCommand; KisKeyframeSP keyframe = createKeyframe(time, KisKeyframeSP(), &tempParentCommand); setScalarValue(keyframe, value); QString interpolationMode = keyframeNode.toElement().attribute("interpolation"); if (interpolationMode == "constant") { keyframe->setInterpolationMode(KisKeyframe::Constant); } else if (interpolationMode == "linear") { keyframe->setInterpolationMode(KisKeyframe::Linear); } else if (interpolationMode == "bezier") { keyframe->setInterpolationMode(KisKeyframe::Bezier); } QString tangentsMode = keyframeNode.toElement().attribute("tangents"); if (tangentsMode == "smooth") { keyframe->setTangentsMode(KisKeyframe::Smooth); } else if (tangentsMode == "sharp") { keyframe->setTangentsMode(KisKeyframe::Sharp); } QPointF leftTangent; QPointF rightTangent; KisDomUtils::loadValue(keyframeNode, "leftTangent", &leftTangent); KisDomUtils::loadValue(keyframeNode, "rightTangent", &rightTangent); keyframe->setInterpolationTangents(leftTangent, rightTangent); return keyframe; } void KisScalarKeyframeChannel::notifyKeyframeChanged(KisKeyframeSP keyframe) { QRect rect = affectedRect(keyframe); KisTimeRange range = affectedFrames(keyframe->time()); requestUpdate(range, rect); emit sigKeyframeChanged(keyframe); } diff --git a/libs/libkis/FilterLayer.cpp b/libs/libkis/FilterLayer.cpp index fdffed5742..46373d49ec 100644 --- a/libs/libkis/FilterLayer.cpp +++ b/libs/libkis/FilterLayer.cpp @@ -1,63 +1,66 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program 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 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 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 "FilterLayer.h" #include #include #include #include #include #include FilterLayer::FilterLayer(KisImageSP image, QString name, Filter &filter, Selection &selection, QObject *parent) : Node(image, new KisAdjustmentLayer(image, name, filter.filterConfig(), selection.selection()), parent) { } FilterLayer::FilterLayer(KisAdjustmentLayerSP layer, QObject *parent): Node(layer->image(), layer, parent) { } FilterLayer::~FilterLayer() { } QString FilterLayer::type() const { return "filterlayer"; } void FilterLayer::setFilter(Filter &filter) { + if (!this->node()) return; KisAdjustmentLayer *layer = dynamic_cast(this->node().data()); //getting the default configuration here avoids trouble with versioning. - layer->setFilter(filter.filterConfig()); + if (layer) { + layer->setFilter(filter.filterConfig()); + } } Filter * FilterLayer::filter() { Filter* filter = new Filter(); const KisAdjustmentLayer *layer = qobject_cast(this->node()); filter->setName(layer->filter()->name()); filter->setConfiguration(new InfoObject(layer->filter())); return filter; } diff --git a/libs/libkis/Shape.cpp b/libs/libkis/Shape.cpp index f66280e947..393e5e7718 100644 --- a/libs/libkis/Shape.cpp +++ b/libs/libkis/Shape.cpp @@ -1,106 +1,106 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program 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 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 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 "Shape.h" #include #include #include #include #include #include struct Shape::Private { Private() {} - KoShape *shape; + KoShape *shape {0}; }; Shape::Shape(KoShape *shape, QObject *parent) : QObject(parent) , d(new Private) { d->shape = shape; } Shape::~Shape() { delete d; } QString Shape::name() const { return d->shape->name(); } void Shape::setName(const QString &name) { d->shape->setName(name); } QString Shape::type() const { return d->shape->shapeId(); } bool Shape::visible() { return d->shape->isVisible(); } void Shape::setVisible(bool visible) { d->shape->setVisible(visible); } QRectF Shape::boundingBox() const { return d->shape->boundingRect(); } QPointF Shape::position() const { return d->shape->position(); } void Shape::setPosition(QPointF point) { d->shape->setPosition(point); } QString Shape::toSvg() { QBuffer shapesBuffer; QBuffer stylesBuffer; shapesBuffer.open(QIODevice::WriteOnly); stylesBuffer.open(QIODevice::WriteOnly); { SvgSavingContext savingContext(shapesBuffer, stylesBuffer); savingContext.setStrippedTextMode(true); SvgWriter writer({d->shape}); writer.saveDetached(savingContext); } shapesBuffer.close(); stylesBuffer.close(); return QString::fromUtf8(shapesBuffer.data()); } KoShape *Shape::shape() { return d->shape; } diff --git a/libs/metadata/kis_meta_data_schema_registry.cc b/libs/metadata/kis_meta_data_schema_registry.cc index c7a35a307c..faaff7a982 100644 --- a/libs/metadata/kis_meta_data_schema_registry.cc +++ b/libs/metadata/kis_meta_data_schema_registry.cc @@ -1,109 +1,111 @@ /* * Copyright (c) 2007,2009 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; 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_schema_registry.h" #include #include #include "kis_debug.h" #include "kis_meta_data_schema_p.h" using namespace KisMetaData; // ---- Schema Registry ---- // struct Q_DECL_HIDDEN SchemaRegistry::Private { static SchemaRegistry *singleton; QHash uri2Schema; QHash prefix2Schema; }; SchemaRegistry *SchemaRegistry::Private::singleton = 0; SchemaRegistry* SchemaRegistry::instance() { if (SchemaRegistry::Private::singleton == 0) { SchemaRegistry::Private::singleton = new SchemaRegistry(); } return SchemaRegistry::Private::singleton; } SchemaRegistry::SchemaRegistry() : d(new Private) { KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); QStringList schemasFilenames = KoResourcePaths::findAllResources("metadata_schema", "*.schema"); Q_FOREACH (const QString& fileName, schemasFilenames) { Schema* schema = new Schema(); schema->d->load(fileName); if (schemaFromUri(schema->uri())) { errMetaData << "Schema already exist uri: " << schema->uri(); + delete schema; } else if (schemaFromPrefix(schema->prefix())) { errMetaData << "Schema already exist prefix: " << schema->prefix(); + delete schema; } else { d->uri2Schema[schema->uri()] = schema; d->prefix2Schema[schema->prefix()] = schema; } } // DEPRECATED WRITE A SCHEMA FOR EACH OF THEM create(Schema::MakerNoteSchemaUri, "mkn"); create(Schema::IPTCSchemaUri, "Iptc4xmpCore"); create(Schema::PhotoshopSchemaUri, "photoshop"); } SchemaRegistry::~SchemaRegistry() { delete d; } const Schema* SchemaRegistry::schemaFromUri(const QString & uri) const { return d->uri2Schema[uri]; } const Schema* SchemaRegistry::schemaFromPrefix(const QString & prefix) const { return d->prefix2Schema[prefix]; } const Schema* SchemaRegistry::create(const QString & uri, const QString & prefix) { // First search for the schema const Schema* schema = schemaFromUri(uri); if (schema) { return schema; } // Second search for the prefix schema = schemaFromPrefix(prefix); if (schema) { return 0; // A schema with the same prefix already exist } // The schema doesn't exist yet, create it Schema* nschema = new Schema(uri, prefix); d->uri2Schema[uri] = nschema; d->prefix2Schema[prefix] = nschema; return nschema; } diff --git a/libs/odf/KoGenStyle.h b/libs/odf/KoGenStyle.h index 913df3ec41..0c86eda16f 100644 --- a/libs/odf/KoGenStyle.h +++ b/libs/odf/KoGenStyle.h @@ -1,549 +1,549 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007-2008 Thorsten Zachmann Copyright (C) 2009 Inge Wallin Copyright (C) 2010 KO GmbH Copyright (C) 2010 Jarosław Staniek Copyright (C) 2011 Pierre Ducroquet 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 KOGENSTYLE_H #define KOGENSTYLE_H #include #include #include #include "kritaodf_export.h" class QTextLength; class KoGenStyles; class KoXmlWriter; /** * A generic style, i.e. basically a collection of properties and a name. * Instances of KoGenStyle can either be held in the KoGenStyles collection, * or created (e.g. on the stack) and given to KoGenStyles::insert(). * * @author David Faure */ class KRITAODF_EXPORT KoGenStyle { public: /** * Possible values for the "type" argument of the KoGenStyle constructor. * @note If there is still something missing, add it here so that it is possible to use the same * saving code in all applications. */ enum Type { PageLayoutStyle, ///< style:page-layout as in odf 14.3 Page Layout TextStyle, ///< style:style from family "text" as in odf 14.8.1 Text Styles ///< (office:styles) TextAutoStyle, ///< style:style from family "text" as in odf 14.8.1 Text Styles ///< (office:automatic-styles) ParagraphStyle, ///< style:style from family "paragraph" as in odf 14.1 Style Element ///< (office:styles) ParagraphAutoStyle, ///< style:style from family "paragraph" as in odf 14.1 Style Element ///< (office:automatic-styles) SectionStyle, ///< style:style from family "section" as in odf 14.8.3 Section Styles ///< (office:styles) SectionAutoStyle, ///< style:style from family "section" as in odf 14.8.3 Section Styles ///< (office:automatic-styles) RubyStyle, ///< style:style from family "ruby" as in odf 14.8.4 Ruby Style ///< (office:styles) RubyAutoStyle, ///< style:style from family "ruby" as in odf 14.8.4 Ruby Style ///< (office:automatic-styles) TableStyle, ///< style:style from family "table" as in odf 14.12 Table Formatting ///< Properties (office:styles) TableAutoStyle, ///< style:style from family "table" as in odf 14.12 Table Formatting Properties ///< (office:automatic-styles) TableColumnStyle, ///< style:style from family "table-column" as in odf 15.9 Column Formatting ///< Properties (office:styles) TableColumnAutoStyle, ///< style:style from family "table-column" as in odf 15.9 Column Formatting ///< Properties (office:automatic-styles) TableRowStyle, ///< style:style from family "table-row" as in odf 15.10 Table Row Formatting ///< Properties (office:styles) TableRowAutoStyle, ///< style:style from family "table-row" as in odf 15.10 Table Row Formatting ///< Properties (office:automatic-styles) TableCellStyle, ///< style:style from family "table-cell" as in odf 15.11 Table Cell Formatting ///< Properties (office:styles) TableCellAutoStyle, ///< style:style from family "table-cell" as in odf 15.11 Table Cell Formatting ///< Properties (office:automatic-styles) GraphicStyle, ///< style:style from family "graphic" as in 14.13.1 Graphic and Presentation ///< Styles (office:automatic-styles) GraphicAutoStyle, ///< style:style from family "graphic" as in 14.13.1 Graphic and Presentation ///< Styles (office:automatic-styles) PresentationStyle, ///< style:style from family "presentation" as in 14.13.1 Graphic and ///< Presentation Styles (office:styles) PresentationAutoStyle, ///< style:style from family "presentation" as in 14.13.1 Graphic and ///< Presentation Styles (office:automatic-styles) DrawingPageStyle, ///< style:style from family "drawing-page" as in odf 14.13.2 Drawing Page Style ///< (office:styles) DrawingPageAutoStyle, ///< style:style from family "drawing-page" as in odf 14.13.2 Drawing Page Style ///< (office:automatic-styles) ChartStyle, ///< style:style from family "chart" as in odf 14.16 Chart Styles ///< (office:styles) ChartAutoStyle, ///< style:style from family "chart" as in odf 14.16 Chart Styles ///< (office:automatic-styles) ListStyle, ///< text:list-style as in odf 14.10 List Style (office:styles) ListAutoStyle, ///< text:list-style as in odf 14.10 List Style (office:automatic-styles) NumericNumberStyle, ///< number:number-style as in odf 14.7.1 Number Style NumericDateStyle, ///< number:date-style as in odf 14.7.4 Date Style NumericTimeStyle, ///< number:time-style as in odf 14.7.5 Time Style NumericFractionStyle, ///< number:number-style as in odf 14.7.1 Number Style NumericPercentageStyle, ///< number:percentage-style as in odf 14.7.3 Percentage Style NumericScientificStyle, ///< number:number-style as in odf 14.7.1 Number Style NumericCurrencyStyle, ///< number:currency-style as in odf 14.7.2 Currency Style NumericTextStyle, ///< number:text-style 14.7.7 Text Style ///< @note unused HatchStyle, ///< draw:hatch as in odf 14.14.3 Hatch (office:styles) StrokeDashStyle, ///< draw:stroke-dash as in odf 14.14.7 Stroke Dash (office:styles) GradientStyle, ///< draw:gradient as in odf 14.14.1 Gradient (office:styles) LinearGradientStyle, ///< svg:linearGradient as in odf 14.14.2 SVG Gradients (office:styles) RadialGradientStyle, ///< svg:radialGradient as in odf 14.14.2 SVG Gradients (office:styles) ConicalGradientStyle, ///< calligra:conicalGradient calligra extension for conical gradients FillImageStyle, ///< draw:fill-image as in odf 14.14.4 Fill Image (office:styles) NumericBooleanStyle, ///< number:boolean 14.7.6 Boolean Style ///< @note unused OpacityStyle, ///< draw:opacity as in odf 14.14.5 Opacity Gradient ///< @note unused MarkerStyle, ///< draw:marker as in odf 14.14.6 Marker PresentationPageLayoutStyle, ///< style:presentation-page-layout as in odf 14.15 Presentation Page Layouts OutlineLevelStyle, ///< text:outline-style as in odf 1.2 section 16.34 // TODO differently MasterPageStyle, ///< style:master-page as in odf 14.4 14.4 Master Pages (office:master-styles) // style:default-style as in odf 14.2 Default Styles // 14.5 Table Templates /// @internal @note always update when adding values to this enum LastStyle = MasterPageStyle }; /** * Start the definition of a new style. Its name will be set later by KoGenStyles::insert(), * but first you must define its properties and attributes. * * @param type this is a hook for the application to categorize styles * See the Style* enum. Ignored when writing out the style. * * @param familyName The value for style:family, e.g. text, paragraph, graphic etc. * The family is for style:style elements only; number styles and list styles don't have one. * * @param parentName If set, name of the parent style from which this one inherits. */ explicit KoGenStyle(Type type = PageLayoutStyle, const char *familyName = 0, const QString &parentName = QString()); ~KoGenStyle(); /** * setAutoStyleInStylesDotXml(true) marks a given automatic style as being needed in styles.xml. * For instance styles used by headers and footers need to go there, since * they are saved in styles.xml, and styles.xml must be independent from content.xml. * * The application should use KoGenStyles::styles( type, true ) in order to retrieve * those styles and save them separately. */ void setAutoStyleInStylesDotXml(bool b) { m_autoStyleInStylesDotXml = b; } /// @return the value passed to setAutoStyleInStylesDotXml; false by default bool autoStyleInStylesDotXml() const { return m_autoStyleInStylesDotXml; } /** * setDefaultStyle(true) marks a given style as being the default style. * This means we expect that you will call writeStyle( ...,"style:default-style"), * and its name will be omitted in the output. */ void setDefaultStyle(bool b) { m_defaultStyle = b; } /// @return the value passed to setDefaultStyle; false by default bool isDefaultStyle() const { return m_defaultStyle; } /// Return the type of this style, as set in the constructor Type type() const { return m_type; } /// Return the family name const char* familyName() const { return m_familyName.data(); } /// Sets the name of style's parent. void setParentName(const QString &name) { m_parentName = name; } /// Return the name of style's parent, if set QString parentName() const { return m_parentName; } /** * @brief The types of properties * * Simple styles only write one foo-properties tag, in which case they can just use DefaultType. * However a given style might want to write several kinds of properties, in which case it would * need to use other property types than the default one. * * For instance this style: * @code * * * * * * @endcode * would use DefaultType for chart-properties (and would pass "style:chart-properties" to writeStyle(), * and would use GraphicType and TextType. */ enum PropertyType { /** * DefaultType depends on family: e.g. paragraph-properties if family=paragraph * or on the type of style (e.g. page-layout -> page-layout-properties). * (In fact that tag name is the one passed to writeStyle) */ DefaultType, /// TextType is always text-properties. TextType, /// ParagraphType is always paragraph-properties. ParagraphType, /// GraphicType is always graphic-properties. GraphicType, /// SectionType is always section-properties. SectionType, /// RubyType is always ruby-properties. RubyType, /// TableType is always table-properties. TableType, /// TableColumnType is always table-column-properties TableColumnType, /// TableRowType is always table-row-properties. TableRowType, /// TableCellType is always for table-cell-properties. TableCellType, /// PresentationType is always for presentation-properties. PresentationType, /// DrawingPageType is always for drawing-page-properties. DrawingPageType, /// ChartType is always for chart-properties. ChartType, Reserved1, ///< @internal for binary compatible extensions /// For elements that are children of the style itself, not any of the properties StyleChildElement, /// @internal @note always update when adding values to this enum LastPropertyType = StyleChildElement }; /// Add a property to the style. Passing DefaultType as property type uses a style-type specific property type. void addProperty(const QString &propName, const QString &propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, propValue); } /// Overloaded version of addProperty that takes a char*, usually for "..." void addProperty(const QString &propName, const char *propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, QString::fromUtf8(propValue)); } /// Overloaded version of addProperty that converts an int to a string void addProperty(const QString &propName, int propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, QString::number(propValue)); } /// Overloaded version of addProperty that converts a bool to a string (false/true) void addProperty(const QString &propName, bool propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, propValue ? "true" : "false"); } /** * Add a property which represents a distance, measured in pt * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits), * and the unit name ("pt") is appended to it. */ void addPropertyPt(const QString &propName, qreal propValue, PropertyType type = DefaultType); /** * Add a property which represents a length, measured in pt, or in percent * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits) or as integer (for percents), * and the unit name ("pt" or "%") is appended to it. */ void addPropertyLength(const QString &propName, const QTextLength &propValue, PropertyType type = DefaultType); /** * Remove a property from the style. Passing DefaultType as property type * uses a style-type specific property type. */ void removeProperty(const QString &propName, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].remove(propName); } /** * Remove properties of defined type from the style. Passing DefaultType * as property type uses a style-type specific property type. */ void removeAllProperties(PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].clear(); } /** * Add an attribute to the style * The difference between property and attributes is a bit oasis-format-specific: * attributes are for the style element itself, and properties are in the style:properties child element */ void addAttribute(const QString &attrName, const QString& attrValue) { m_attributes.insert(attrName, attrValue); } /// Overloaded version of addAttribute that takes a char*, usually for "..." void addAttribute(const QString &attrName, const char* attrValue) { m_attributes.insert(attrName, QString::fromUtf8(attrValue)); } /// Overloaded version of addAttribute that converts an int to a string void addAttribute(const QString &attrName, int attrValue) { m_attributes.insert(attrName, QString::number(attrValue)); } /// Overloaded version of addAttribute that converts a bool to a string void addAttribute(const QString &attrName, bool attrValue) { m_attributes.insert(attrName, attrValue ? "true" : "false"); } /** * Add an attribute which represents a distance, measured in pt * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits), * and the unit name ("pt") is appended to it. */ void addAttribute(const QString &attrName, qreal attrValue); /** * Add an attribute that represents a percentage value as defined in ODF */ void addAttributePercent(const QString &attrName, qreal value); /** * Add an attribute that represents a percentage value as defined in ODF */ void addAttributePercent(const QString &attrName, int value); /** * Remove an attribute from the style. */ void removeAttribute(const QString &attrName) { m_attributes.remove(attrName); } /** * @brief Add a child element to the style properties. * * What is meant here is that the contents of the QString * will be written out literally. This means you should use * KoXmlWriter to generate it: * @code * QBuffer buffer; * buffer.open( QIODevice::WriteOnly ); * KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level * elementWriter.startElement( "..." ); * ... * elementWriter.endElement(); * QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); * gs.addChildElement( "...", elementContents ); * @endcode * * The value of @p elementName is only used to set the order on how the child elements are written out. */ void addChildElement(const QString &elementName, const QString& elementContents, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_childProperties[type].insert(elementName, elementContents); } /** * Same like \a addChildElement above but with QByteArray to explicit convert from QByteArray * to QString using utf8 to prevent a dirty pitfall. */ void addChildElement(const QString &elementName, const QByteArray& elementContents, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_childProperties[type].insert(elementName, QString::fromUtf8(elementContents)); } /** * Same like \a addChildElement above but adds a child style which is not child of any of the properties * The value of @p elementName is only used to set the order on how the child elements are written out. */ void addStyleChildElement(const QString &elementName, const QString& elementContents) { m_properties[StyleChildElement].insertMulti(elementName, elementContents); } /** * Same like \a addStyleChildElement above but with QByteArray to explicit convert from QByteArray * to QString using utf8 to prevent a dirty pitfall. * The value of @p elementName is only used to set the order on how the child elements are written out. */ void addStyleChildElement(const QString &elementName, const QByteArray& elementContents) { m_properties[StyleChildElement].insertMulti(elementName, QString::fromUtf8(elementContents)); } /** * @brief Add a style:map to the style. * @param styleMap the attributes for the map, associated as (name,value). */ void addStyleMap(const QMap &styleMap); /** * @return true if the style has no attributes, no properties, no style map etc. * This can be used by applications which do not save all attributes unconditionally, * but only those that differ from the parent. But note that KoGenStyles::insert() can't find this out... */ bool isEmpty() const; /** * Write the definition of this style to @p writer, using the OASIS format. * @param writer the KoXmlWriter in which @p elementName will be created and filled in * @param styles the styles collection, used to look up the parent style * @param elementName the name of the XML element, e.g. "style:style". Don't forget to * pass style:default-style if isDefaultStyle(). * @param name must come from the collection. It will be ignored if isDefaultStyle() is true. * @param propertiesElementName the name of the XML element with the style properties, * e.g. "style:text-properties". Can be 0 in special cases where there should be no such item, * in which case the attributes and elements are added under the style itself. * @param closeElement set it to false to be able to add more child elements to the style element * @param drawElement set it to true to add "draw:name" (used for gradient/hatch style) otherwise add "style:name" */ void writeStyle(KoXmlWriter *writer, const KoGenStyles &styles, const char *elementName, const QString &name, const char *propertiesElementName, bool closeElement = true, bool drawElement = false) const; /** * Write the definition of these style properties to @p writer, using the OASIS format. * @param writer the KoXmlWriter in which @p elementName will be created and filled in * @param type the type of properties to write * @param parentStyle the parent to this style */ void writeStyleProperties(KoXmlWriter *writer, PropertyType type, const KoGenStyle *parentStyle = 0) const; /** * QMap requires a complete sorting order. * Another solution would have been a qdict and a key() here, a la KoTextFormat, * but the key was difficult to generate. * Solutions with only a hash value (not representative of the whole data) * require us to write a hashtable by hand.... */ bool operator<(const KoGenStyle &other) const; /// Not needed for QMap, but can still be useful bool operator==(const KoGenStyle &other) const; /** * Returns a property of this style. In prinicpal this class is meant to be write-only, but * some exceptional cases having read-support as well is very useful. Passing DefaultType * as property type uses a style-type specific property type. */ QString property(const QString &propName, PropertyType type = DefaultType) const { if (type == DefaultType) { type = m_propertyType; } const QMap::const_iterator it = m_properties[type].constFind(propName); if (it != m_properties[type].constEnd()) return it.value(); return QString(); } /** * Returns a property of this style. In prinicpal this class is meant to be write-only, but * some exceptional cases having read-support as well is very useful. Passing DefaultType * as property type uses a style-type specific property type. */ QString childProperty(const QString &propName, PropertyType type = DefaultType) const { if (type == DefaultType) { type = m_propertyType; } const QMap::const_iterator it = m_childProperties[type].constFind(propName); if (it != m_childProperties[type].constEnd()) return it.value(); return QString(); } /// Returns an attribute of this style. In prinicpal this class is meant to be write-only, but some exceptional cases having read-support as well is very useful. QString attribute(const QString &propName) const { const QMap::const_iterator it = m_attributes.constFind(propName); if (it != m_attributes.constEnd()) return it.value(); return QString(); } /** * Copies properties of defined type from a style to another style. * This is needed in rare cases where two styles have properties of different types * and we want to merge them to one style. */ static void copyPropertiesFromStyle(const KoGenStyle &sourceStyle, KoGenStyle &targetStyle, PropertyType type = DefaultType); private: #ifndef NDEBUG void printDebug() const; #endif private: // Note that the copy constructor and assignment operator are allowed. // Better not use pointers below! // TODO turn this into a QSharedData class PropertyType m_propertyType; Type m_type; QByteArray m_familyName; QString m_parentName; /// We use QMaps since they provide automatic sorting on the key (important for unicity!) typedef QMap StyleMap; StyleMap m_properties[LastPropertyType+1]; StyleMap m_childProperties[LastPropertyType+1]; StyleMap m_attributes; QList m_maps; // we can't really sort the maps between themselves... bool m_autoStyleInStylesDotXml; bool m_defaultStyle; - short m_unused2; + short m_unused2 {0}; // For insert() friend class KoGenStyles; }; #endif /* KOGENSTYLE_H */ diff --git a/libs/pigment/compositeops/KoCompositeOpGreater.h b/libs/pigment/compositeops/KoCompositeOpGreater.h index 9d638d6564..ba22a127ea 100644 --- a/libs/pigment/compositeops/KoCompositeOpGreater.h +++ b/libs/pigment/compositeops/KoCompositeOpGreater.h @@ -1,104 +1,106 @@ /* * Copyright (c) 2014 Nicholas Guttenberg * * Based on KoCompositeOpBehind.h, * Copyright (c) 2012 José Luis Vergara * * 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 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 _KOCOMPOSITEOPGREATER_H_ #define _KOCOMPOSITEOPGREATER_H_ #include "KoCompositeOpBase.h" /** * Greater-than compositor - uses the greater of two alpha values to determine the color */ template class KoCompositeOpGreater : public KoCompositeOpBase > { typedef KoCompositeOpBase > base_class; typedef typename CS_Traits::channels_type channels_type; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; static const qint8 channels_nb = CS_Traits::channels_nb; static const qint8 alpha_pos = CS_Traits::alpha_pos; public: KoCompositeOpGreater(const KoColorSpace * cs) : base_class(cs, COMPOSITE_GREATER, i18n("Greater"), KoCompositeOp::categoryMix()) { } public: template inline static channels_type composeColorChannels(const channels_type* src, channels_type srcAlpha, channels_type* dst, channels_type dstAlpha, channels_type maskAlpha, channels_type opacity, const QBitArray& channelFlags ) { using namespace Arithmetic; if (dstAlpha == unitValue()) return dstAlpha; channels_type appliedAlpha = mul(maskAlpha, srcAlpha, opacity); if (appliedAlpha == zeroValue()) return dstAlpha; channels_type newDstAlpha; float dA = scale(dstAlpha); float w = 1.0/(1.0+exp(-40.0*(dA - scale(appliedAlpha)))); float a = dA*w + scale(appliedAlpha)*(1.0-w); if (a < 0.0f) { a = 0.0f; } if (a > 1.0f) { a = 1.0f; } // For a standard Over, the resulting alpha is: a = opacity*dstAlpha + (1-opacity)*srcAlpha // Let us assume we're blending with a color with srcAlpha = 1 here // Therefore, opacity = (1.0 - a)/(1.0 - dstAlpha) if (a(a); if (dstAlpha != zeroValue()) { for (qint8 channel = 0; channel < channels_nb; ++channel) if(channel != alpha_pos && (allChannelFlags || channelFlags.testBit(channel))) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; channels_type dstMult = mul(dst[channel], dstAlpha); channels_type srcMult = mul(src[channel], unitValue()); channels_type blendedValue = lerp(dstMult, srcMult, scale(fakeOpacity)); - + // CID 249016 (#1 of 15): + // Division or modulo by zero (DIVIDE_BY_ZERO)12. divide_by_zero: In function call divide, division by expression newDstAlpha which may be zero has undefined behavior. + if (newDstAlpha == 0) newDstAlpha = 1; composite_type normedValue = KoColorSpaceMaths::divide(blendedValue, newDstAlpha); dst[channel] = KoColorSpaceMaths::clampAfterScale(normedValue); } } else { // don't blend if the color of the destination is undefined (has zero opacity) // copy the source channel instead for (qint8 channel = 0; channel < channels_nb; ++channel) if(channel != alpha_pos && (allChannelFlags || channelFlags.testBit(channel))) dst[channel] = src[channel]; } return newDstAlpha; } }; #endif // _KOCOMPOSITEOPGREATER_H_ diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index a189a19eb9..c29cbb36eb 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2754 +1,2754 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port 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" #include // qt includes #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 "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In ths case we should clear oll the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } bool KisMainWindow::canvasDetached() const { return centralWidget() != d->widgetStack; } void KisMainWindow::setCanvasDetached(bool detach) { if (detach == canvasDetached()) return; QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); if (incomingWidget) { setCentralWidget(incomingWidget); } if (detach) { d->canvasWindow->show(); } else { d->canvasWindow->hide(); } } void KisMainWindow::slotFileSelected(QString path) { QString url = path; if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url), Import); if (!res) { warnKrita << "Loading" << url << "failed"; } } } void KisMainWindow::slotEmptyFilePath() { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?")); } QWidget * KisMainWindow::canvasWindow() const { return d->canvasWindow; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->slotClearRecentFiles(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); - if (isExporting && !d->lastExportLocation.isEmpty()) { + if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } #include diff --git a/libs/ui/KisPaletteEditor.cpp b/libs/ui/KisPaletteEditor.cpp index 41f73684bf..161ccfd099 100644 --- a/libs/ui/KisPaletteEditor.cpp +++ b/libs/ui/KisPaletteEditor.cpp @@ -1,689 +1,689 @@ /* * Copyright (c) 2018 Michael Zhou * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPaletteEditor.h" struct KisPaletteEditor::PaletteInfo { QString name; QString filename; int columnCount; bool isGlobal; bool isReadOnly; QHash groups; }; struct KisPaletteEditor::Private { bool isGlobalModified {false}; bool isNameModified {false}; bool isFilenameModified {false}; bool isColumnCountModified {false}; QSet modifiedGroupNames; // key is original group name QSet newGroupNames; QSet keepColorGroups; QSet pathsToRemove; QString groupBeingRenamed; QPointer model; QPointer view; PaletteInfo modified; QPointer query; KoResourceServer *rServer {0}; QPalette normalPalette; QPalette warnPalette; }; KisPaletteEditor::KisPaletteEditor(QObject *parent) : QObject(parent) , m_d(new Private) { m_d->rServer = KoResourceServerProvider::instance()->paletteServer(); m_d->warnPalette.setColor(QPalette::Text, Qt::red); } KisPaletteEditor::~KisPaletteEditor() { } void KisPaletteEditor::setPaletteModel(KisPaletteModel *model) { if (!model) { return; } m_d->model = model; slotPaletteChanged(); connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotPaletteChanged())); connect(model, SIGNAL(sigPaletteModified()), SLOT(slotSetDocumentModified())); } void KisPaletteEditor::setView(KisViewManager *view) { m_d->view = view; } void KisPaletteEditor::addPalette() { if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoDialog dlg; QFormLayout layout; dlg.mainWidget()->setLayout(&layout); QLabel lbl(i18nc("Label for line edit to set a palette name.","Name")); QLineEdit le(i18nc("Default name for a new palette","New Palette")); layout.addRow(&lbl, &le); QLabel lbl2(i18nc("Label for line edit to set a palette filename.","File Name")); QLineEdit le2(i18nc("Default file name for a new palette", "New Palette")); layout.addRow(&lbl2, &le2); QCheckBox chkSaveInDocument(i18n("Save Palette in the Current Document")); chkSaveInDocument.setChecked(false); layout.addRow(&chkSaveInDocument); if (dlg.exec() != QDialog::Accepted) { return; } KoColorSet *newColorSet = new KoColorSet(newPaletteFileName(!chkSaveInDocument.isChecked(), le2.text())); newColorSet->setPaletteType(KoColorSet::KPL); newColorSet->setIsGlobal(!chkSaveInDocument.isChecked()); newColorSet->setIsEditable(true); newColorSet->setValid(true); newColorSet->setName(le.text()); m_d->rServer->addResource(newColorSet, !chkSaveInDocument.isChecked()); m_d->rServer->removeFromBlacklist(newColorSet); uploadPaletteList(); } void KisPaletteEditor::importPalette() { KoFileDialog dialog(0, KoFileDialog::OpenFile, "Open Palette"); dialog.setDefaultDir(QDir::homePath()); dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset" << "application/x-gimp-color-palette"); QString filename = dialog.filename(); if (filename.isEmpty()) { return; } if (duplicateExistsFilename(filename, false)) { QMessageBox message; message.setWindowTitle(i18n("Can't Import Palette")); message.setText(i18n("Can't import palette: there's already imported with the same filename")); message.exec(); return; } QMessageBox messageBox; messageBox.setText(i18n("Do you want to store this palette in your current image?")); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); bool global = (messageBox.exec() == QMessageBox::Yes); KoColorSet *colorSet = new KoColorSet(filename); colorSet->load(); QString name = filenameFromPath(colorSet->filename()); if (duplicateExistsFilename(name, false)) { colorSet->setFilename(newPaletteFileName(global)); } else { colorSet->setFilename(name); } colorSet->setIsGlobal(global); m_d->rServer->addResource(colorSet, global); m_d->rServer->removeFromBlacklist(colorSet); uploadPaletteList(); } void KisPaletteEditor::removePalette(KoColorSet *cs) { if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!cs || !cs->isEditable()) { return; } if (cs->isGlobal()) { - m_d->rServer->removeResourceAndBlacklist(cs); QFile::remove(cs->filename()); + m_d->rServer->removeResourceAndBlacklist(cs); return; } m_d->rServer->removeResourceFromServer(cs); uploadPaletteList(); } int KisPaletteEditor::rowNumberOfGroup(const QString &oriName) const { if (!m_d->modified.groups.contains(oriName)) { return 0; } return m_d->modified.groups[oriName].rowCount(); } bool KisPaletteEditor::duplicateExistsGroupName(const QString &name) const { if (name == m_d->groupBeingRenamed) { return false; } Q_FOREACH (const KisSwatchGroup &g, m_d->modified.groups.values()) { if (name == g.name()) { return true; } } return false; } bool KisPaletteEditor::duplicateExistsOriginalGroupName(const QString &name) const { return m_d->modified.groups.contains(name); } QString KisPaletteEditor::oldNameFromNewName(const QString &newName) const { Q_FOREACH (const QString &oldGroupName, m_d->modified.groups.keys()) { if (m_d->modified.groups[oldGroupName].name() == newName) { return oldGroupName; } } return QString(); } void KisPaletteEditor::rename(const QString &newName) { if (newName.isEmpty()) { return; } m_d->isNameModified = true; m_d->modified.name = newName; } void KisPaletteEditor::changeFilename(const QString &newName) { if (newName.isEmpty()) { return; } m_d->isFilenameModified = true; m_d->pathsToRemove.insert(m_d->modified.filename); if (m_d->modified.isGlobal) { m_d->modified.filename = m_d->rServer->saveLocation() + newName; } else { m_d->modified.filename = newName; } } void KisPaletteEditor::changeColCount(int newCount) { m_d->isColumnCountModified = true; m_d->modified.columnCount = newCount; } QString KisPaletteEditor::addGroup() { KoDialog dlg; m_d->query = &dlg; QVBoxLayout layout(&dlg); dlg.mainWidget()->setLayout(&layout); QLabel lblName(i18n("Name"), &dlg); layout.addWidget(&lblName); QLineEdit leName(&dlg); leName.setText(newGroupName()); connect(&leName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString))); layout.addWidget(&leName); QLabel lblRowCount(i18n("Row count"), &dlg); layout.addWidget(&lblRowCount); QSpinBox spxRow(&dlg); spxRow.setValue(20); layout.addWidget(&spxRow); if (dlg.exec() != QDialog::Accepted) { return QString(); } if (duplicateExistsGroupName(leName.text())) { return QString(); } QString realName = leName.text(); QString name = realName; if (duplicateExistsOriginalGroupName(name)) { name = newGroupName(); } m_d->modified.groups[name] = KisSwatchGroup(); KisSwatchGroup &newGroup = m_d->modified.groups[name]; newGroup.setName(realName); m_d->newGroupNames.insert(name); newGroup.setRowCount(spxRow.value()); return realName; } bool KisPaletteEditor::removeGroup(const QString &name) { KoDialog window; window.setWindowTitle(i18nc("@title:window", "Removing Group")); QFormLayout editableItems(&window); QCheckBox chkKeep(&window); window.mainWidget()->setLayout(&editableItems); editableItems.addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), &chkKeep); if (window.exec() != KoDialog::Accepted) { return false; } m_d->modified.groups.remove(name); m_d->newGroupNames.remove(name); if (chkKeep.isChecked()) { m_d->keepColorGroups.insert(name); } return true; } QString KisPaletteEditor::renameGroup(const QString &oldName) { if (oldName.isEmpty() || oldName == KoColorSet::GLOBAL_GROUP_NAME) { return QString(); } KoDialog dlg; m_d->query = &dlg; m_d->groupBeingRenamed = m_d->modified.groups[oldName].name(); QFormLayout form(&dlg); dlg.mainWidget()->setLayout(&form); QLineEdit leNewName; connect(&leNewName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString))); leNewName.setText(m_d->modified.groups[oldName].name()); form.addRow(i18nc("Renaming swatch group", "New name"), &leNewName); if (dlg.exec() != KoDialog::Accepted) { return QString(); } if (leNewName.text().isEmpty()) { return QString(); } if (duplicateExistsGroupName(leNewName.text())) { return QString(); } m_d->modified.groups[oldName].setName(leNewName.text()); m_d->modifiedGroupNames.insert(oldName); return leNewName.text(); } void KisPaletteEditor::slotGroupNameChanged(const QString &newName) { QLineEdit *leGroupName = qobject_cast(sender()); if (duplicateExistsGroupName(newName) || newName == QString()) { leGroupName->setPalette(m_d->warnPalette); if (m_d->query->button(KoDialog::Ok)) { m_d->query->button(KoDialog::Ok)->setEnabled(false); } return; } leGroupName->setPalette(m_d->normalPalette); if (m_d->query->button(KoDialog::Ok)) { m_d->query->button(KoDialog::Ok)->setEnabled(true); } } void KisPaletteEditor::changeGroupRowCount(const QString &name, int newRowCount) { if (!m_d->modified.groups.contains(name)) { return; } m_d->modified.groups[name].setRowCount(newRowCount); m_d->modifiedGroupNames.insert(name); } void KisPaletteEditor::setGlobal(bool isGlobal) { m_d->isGlobalModified = true; m_d->modified.isGlobal = isGlobal; } void KisPaletteEditor::setEntry(const KoColor &color, const QModelIndex &index) { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KisSwatch c = KisSwatch(color); c.setId(QString::number(m_d->model->colorSet()->colorCount() + 1)); c.setName(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1))); m_d->model->setEntry(c, index); } void KisPaletteEditor::slotSetDocumentModified() { m_d->view->document()->setModified(true); } void KisPaletteEditor::removeEntry(const QModelIndex &index) { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { removeGroup(qvariant_cast(index.data(KisPaletteModel::GroupNameRole))); updatePalette(); } else { m_d->model->removeEntry(index, false); } if (m_d->model->colorSet()->isGlobal()) { m_d->model->colorSet()->save(); return; } } void KisPaletteEditor::modifyEntry(const QModelIndex &index) { if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoDialog dlg; dlg.setCaption(i18nc("@title:window", "Add a Color")); QFormLayout *editableItems = new QFormLayout(&dlg); dlg.mainWidget()->setLayout(editableItems); QString groupName = qvariant_cast(index.data(Qt::DisplayRole)); if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { renameGroup(groupName); updatePalette(); } else { QLineEdit *lnIDName = new QLineEdit(&dlg); QLineEdit *lnGroupName = new QLineEdit(&dlg); KisColorButton *bnColor = new KisColorButton(&dlg); QCheckBox *chkSpot = new QCheckBox(&dlg); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); KisSwatch entry = m_d->model->getEntry(index); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18nc("Name for a swatch group", "Swatch group name"), lnGroupName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18n("Spot color"), chkSpot); lnGroupName->setText(entry.name()); lnIDName->setText(entry.id()); bnColor->setColor(entry.color()); chkSpot->setChecked(entry.spotColor()); if (dlg.exec() == KoDialog::Accepted) { entry.setName(lnGroupName->text()); entry.setId(lnIDName->text()); entry.setColor(bnColor->color()); entry.setSpotColor(chkSpot->isChecked()); m_d->model->setEntry(entry, index); } } } void KisPaletteEditor::addEntry(const KoColor &color) { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()->isEditable()) { return; } KoDialog window; window.setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); QFormLayout editableItems(&window); window.mainWidget()->setLayout(&editableItems); QComboBox cmbGroups(&window); cmbGroups.addItems(m_d->model->colorSet()->getGroupNames()); QLineEdit lnIDName(&window); QLineEdit lnName(&window); KisColorButton bnColor(&window); QCheckBox chkSpot(&window); chkSpot.setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems.addRow(i18n("Group"), &cmbGroups); editableItems.addRow(i18n("ID"), &lnIDName); editableItems.addRow(i18n("Name"), &lnName); editableItems.addRow(i18n("Color"), &bnColor); editableItems.addRow(i18nc("Spot color", "Spot"), &chkSpot); cmbGroups.setCurrentIndex(0); lnName.setText(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1))); lnIDName.setText(QString::number(m_d->model->colorSet()->colorCount() + 1)); bnColor.setColor(color); chkSpot.setChecked(false); if (window.exec() != KoDialog::Accepted) { return; } QString groupName = cmbGroups.currentText(); KisSwatch newEntry; newEntry.setColor(bnColor.color()); newEntry.setName(lnName.text()); newEntry.setId(lnIDName.text()); newEntry.setSpotColor(chkSpot.isChecked()); m_d->model->addEntry(newEntry, groupName); if (m_d->model->colorSet()->isGlobal()) { m_d->model->colorSet()->save(); return; } m_d->modifiedGroupNames.insert(groupName); m_d->modified.groups[groupName].addEntry(newEntry); } void KisPaletteEditor::updatePalette() { Q_ASSERT(m_d->model); Q_ASSERT(m_d->model->colorSet()); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoColorSet *palette = m_d->model->colorSet(); PaletteInfo &modified = m_d->modified; if (m_d->isColumnCountModified) { palette->setColumnCount(modified.columnCount); } if (m_d->isNameModified) { palette->setName(modified.name); } if (m_d->isFilenameModified) { QString originalPath = palette->filename(); palette->setFilename(modified.filename); if (palette->isGlobal()) { if (!palette->save()) { palette->setFilename(newPaletteFileName(true)); palette->save(); } QFile::remove(originalPath); } } if (m_d->isGlobalModified) { palette->setIsGlobal(modified.isGlobal); if (modified.isGlobal) { setGlobal(); } else { setNonGlobal(); } } Q_FOREACH (const QString &groupName, palette->getGroupNames()) { if (!modified.groups.contains(groupName)) { m_d->model->removeGroup(groupName, m_d->keepColorGroups.contains(groupName)); } } m_d->keepColorGroups.clear(); Q_FOREACH (const QString &groupName, palette->getGroupNames()) { if (m_d->modifiedGroupNames.contains(groupName)) { m_d->model->setRowNumber(groupName, modified.groups[groupName].rowCount()); if (groupName != modified.groups[groupName].name()) { m_d->model->renameGroup(groupName, modified.groups[groupName].name()); modified.groups[modified.groups[groupName].name()] = modified.groups[groupName]; modified.groups.remove(groupName); } } } m_d->modifiedGroupNames.clear(); Q_FOREACH (const QString &newGroupName, m_d->newGroupNames) { m_d->model->addGroup(modified.groups[newGroupName]); } m_d->newGroupNames.clear(); if (m_d->model->colorSet()->isGlobal()) { m_d->model->colorSet()->save(); } } void KisPaletteEditor::slotPaletteChanged() { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()) { return; } KoColorSet *palette = m_d->model->colorSet(); m_d->modified.groups.clear(); m_d->keepColorGroups.clear(); m_d->newGroupNames.clear(); m_d->modifiedGroupNames.clear(); m_d->modified.name = palette->name(); m_d->modified.filename = palette->filename(); m_d->modified.columnCount = palette->columnCount(); m_d->modified.isGlobal = palette->isGlobal(); m_d->modified.isReadOnly = !palette->isEditable(); Q_FOREACH (const QString &groupName, palette->getGroupNames()) { KisSwatchGroup *cs = palette->getGroup(groupName); m_d->modified.groups[groupName] = KisSwatchGroup(*cs); } } void KisPaletteEditor::setGlobal() { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()) { return; } KoColorSet *colorSet = m_d->model->colorSet(); QString saveLocation = m_d->rServer->saveLocation(); QString name = filenameFromPath(colorSet->filename()); QFileInfo fileInfo(saveLocation + name); colorSet->setFilename(fileInfo.filePath()); colorSet->setIsGlobal(true); m_d->rServer->removeFromBlacklist(colorSet); if (!colorSet->save()) { QMessageBox message; message.setWindowTitle(i18n("Saving palette failed")); message.setText(i18n("Failed to save global palette file. Please set it to non-global, or you will lose the file when you close Krita")); message.exec(); } uploadPaletteList(); } bool KisPaletteEditor::duplicateExistsFilename(const QString &filename, bool global) const { QString prefix; if (global) { prefix = m_d->rServer->saveLocation(); } Q_FOREACH (const KoResource *r, KoResourceServerProvider::instance()->paletteServer()->resources()) { if (r->filename() == prefix + filename && r != m_d->model->colorSet()) { return true; } } return false; } QString KisPaletteEditor::relativePathFromSaveLocation() const { return filenameFromPath(m_d->modified.filename); } void KisPaletteEditor::setNonGlobal() { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()) { return; } KoColorSet *colorSet = m_d->model->colorSet(); QString name = filenameFromPath(colorSet->filename()); QFile::remove(colorSet->filename()); if (duplicateExistsFilename(name, false)) { colorSet->setFilename(newPaletteFileName(false)); } else { colorSet->setFilename(name); } colorSet->setIsGlobal(false); uploadPaletteList(); } QString KisPaletteEditor::newPaletteFileName(bool isGlobal, const QString &filename) { QSet nameSet; Q_FOREACH (const KoResource *r, m_d->rServer->resources()) { nameSet.insert(r->filename()); } KoColorSet tmpColorSet; QString result = (filename.isEmpty() ? "new_palette" : filename); if (isGlobal) { result = m_d->rServer->saveLocation() + result; } int i = 0; while (nameSet.contains(result + QString::number(i) + tmpColorSet.defaultFileExtension())) { i++; } result = result + (i > 0 ? QString::number(i) : "") + tmpColorSet.defaultFileExtension(); return result; } QString KisPaletteEditor::newGroupName() const { int i = 1; QString groupname = i18nc("Default new group name", "New Group %1", QString::number(i)); while (m_d->modified.groups.contains(groupname)) { i++; groupname = i18nc("Default new group name", "New Group %1", QString::number(i)); } return groupname; } void KisPaletteEditor::uploadPaletteList() const { QList list; Q_FOREACH (KoResource * paletteResource, m_d->rServer->resources()) { KoColorSet *palette = static_cast(paletteResource); Q_ASSERT(palette); if (!palette->isGlobal()) { list.append(palette); } } m_d->view->document()->setPaletteList(list); } QString KisPaletteEditor::filenameFromPath(const QString &path) const { return QDir::fromNativeSeparators(path).section('/', -1, -1); } diff --git a/libs/ui/KisReferenceImage.cpp b/libs/ui/KisReferenceImage.cpp index c7d1b195a8..1ec55df539 100644 --- a/libs/ui/KisReferenceImage.cpp +++ b/libs/ui/KisReferenceImage.cpp @@ -1,361 +1,361 @@ /* * Copyright (C) 2017 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 "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct KisReferenceImage::Private : public QSharedData { // Filename within .kra (for embedding) QString internalFilename; // File on disk (for linking) QString externalFilename; QImage image; QImage cachedImage; KisQImagePyramid mipmap; qreal saturation{1.0}; int id{-1}; bool embed{true}; bool loadFromFile() { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!externalFilename.isEmpty(), false); return image.load(externalFilename); } bool loadFromClipboard() { image = KisClipboardUtil::getImageFromClipboard(); return !image.isNull(); } void updateCache() { if (saturation < 1.0) { cachedImage = KritaUtils::convertQImageToGrayA(image); if (saturation > 0.0) { QPainter gc2(&cachedImage); gc2.setOpacity(saturation); gc2.drawImage(QPoint(), image); } } else { cachedImage = image; } mipmap = KisQImagePyramid(cachedImage); } }; KisReferenceImage::SetSaturationCommand::SetSaturationCommand(const QList &shapes, qreal newSaturation, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Set saturation"), parent) , newSaturation(newSaturation) { images.reserve(shapes.count()); Q_FOREACH(auto *shape, shapes) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(reference); images.append(reference); } Q_FOREACH(auto *image, images) { oldSaturations.append(image->saturation()); } } void KisReferenceImage::SetSaturationCommand::undo() { auto saturationIterator = oldSaturations.begin(); Q_FOREACH(auto *image, images) { image->setSaturation(*saturationIterator); image->update(); saturationIterator++; } } void KisReferenceImage::SetSaturationCommand::redo() { Q_FOREACH(auto *image, images) { image->setSaturation(newSaturation); image->update(); } } KisReferenceImage::KisReferenceImage() : d(new Private()) { setKeepAspectRatio(true); } KisReferenceImage::KisReferenceImage(const KisReferenceImage &rhs) : KoTosContainer(rhs) , d(rhs.d) {} KisReferenceImage::~KisReferenceImage() {} KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent) { KisReferenceImage *reference = new KisReferenceImage(); reference->d->externalFilename = filename; bool ok = reference->d->loadFromFile(); if (ok) { QRect r = QRect(QPoint(), reference->d->image.size()); QSizeF shapeSize = converter.imageToDocument(r).size(); reference->setSize(shapeSize); } else { delete reference; if (parent) { QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename)); } return nullptr; } return reference; } KisReferenceImage *KisReferenceImage::fromClipboard(const KisCoordinatesConverter &converter) { KisReferenceImage *reference = new KisReferenceImage(); bool ok = reference->d->loadFromClipboard(); if (ok) { QRect r = QRect(QPoint(), reference->d->image.size()); QSizeF size = converter.imageToDocument(r).size(); reference->setSize(size); } else { delete reference; reference = nullptr; } return reference; } void KisReferenceImage::paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &/*paintcontext*/) { if (!parent()) return; gc.save(); applyConversion(gc, converter); QSizeF shapeSize = size(); QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height()); if (d->cachedImage.isNull()) { d->updateCache(); } qreal scale; QImage prescaled = d->mipmap.getClosest(gc.transform() * transform, &scale); transform.scale(1.0 / scale, 1.0 / scale); gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip); gc.setTransform(transform, true); gc.drawImage(QPoint(), prescaled); gc.restore(); } void KisReferenceImage::setSaturation(qreal saturation) { d->saturation = saturation; d->cachedImage = QImage(); } qreal KisReferenceImage::saturation() const { return d->saturation; } void KisReferenceImage::setEmbed(bool embed) { KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty()); d->embed = embed; } bool KisReferenceImage::embed() { return d->embed; } bool KisReferenceImage::hasLocalFile() { return !d->externalFilename.isEmpty(); } QString KisReferenceImage::filename() const { return d->externalFilename; } QString KisReferenceImage::internalFile() const { return d->internalFilename; } void KisReferenceImage::setFilename(const QString &filename) { d->externalFilename = filename; d->embed = false; } QColor KisReferenceImage::getPixel(QPointF position) { if (transparency() == 1.0) return Qt::transparent; const QSizeF shapeSize = size(); const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height()); const QTransform transform = absoluteTransformation(nullptr).inverted() * scale; const QPointF localPosition = position * transform; if (d->cachedImage.isNull()) { d->updateCache(); } return d->cachedImage.pixelColor(localPosition.toPoint()); } void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id) { d->id = id; QDomElement element = document.createElement("referenceimage"); if (d->embed) { d->internalFilename = QString("reference_images/%1.png").arg(id); } const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename); element.setAttribute("src", src); const QSizeF &shapeSize = size(); element.setAttribute("width", KisDomUtils::toString(shapeSize.width())); element.setAttribute("height", KisDomUtils::toString(shapeSize.height())); element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false"); element.setAttribute("transform", SvgUtil::transformToString(transform())); element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency())); element.setAttribute("saturation", KisDomUtils::toString(d->saturation)); parentElement.appendChild(element); } KisReferenceImage * KisReferenceImage::fromXml(const QDomElement &elem) { auto *reference = new KisReferenceImage(); const QString &src = elem.attribute("src"); if (src.startsWith("file://")) { reference->d->externalFilename = src.mid(7); reference->d->embed = false; } else { reference->d->internalFilename = src; reference->d->embed = true; } qreal width = KisDomUtils::toDouble(elem.attribute("width", "100")); qreal height = KisDomUtils::toDouble(elem.attribute("height", "100")); reference->setSize(QSizeF(width, height)); reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true"); auto transform = SvgTransformParser(elem.attribute("transform")).transform(); reference->setTransformation(transform); qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1")); reference->setTransparency(1.0 - opacity); - qreal saturation = KisDomUtils::toDouble(elem.attribute("opacity", "1")); + qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1")); reference->setSaturation(saturation); return reference; } bool KisReferenceImage::saveImage(KoStore *store) const { if (!d->embed) return true; if (!store->open(d->internalFilename)) { return false; } bool saved = false; KoStoreDevice storeDev(store); if (storeDev.open(QIODevice::WriteOnly)) { saved = d->image.save(&storeDev, "PNG"); } return store->close() && saved; } bool KisReferenceImage::loadImage(KoStore *store) { if (!d->embed) { return d->loadFromFile(); } if (!store->open(d->internalFilename)) { return false; } KoStoreDevice storeDev(store); if (!storeDev.open(QIODevice::ReadOnly)) { return false; } if (!d->image.load(&storeDev, "PNG")) { return false; } return store->close(); } KoShape *KisReferenceImage::cloneShape() const { return new KisReferenceImage(*this); } diff --git a/libs/ui/KisTemplateTree.cpp b/libs/ui/KisTemplateTree.cpp index a6312d8d25..fbb5a5da37 100644 --- a/libs/ui/KisTemplateTree.cpp +++ b/libs/ui/KisTemplateTree.cpp @@ -1,289 +1,293 @@ /* This file is part of the KDE project Copyright (C) 2000 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 "KisTemplateTree.h" #include #include #include #include #include #include #include #include #include #include #include #include KisTemplateTree::KisTemplateTree(const QString &templatesResourcePath, bool readTree) : m_templatesResourcePath(templatesResourcePath) , m_defaultGroup(0) , m_defaultTemplate(0) { if (readTree) readTemplateTree(); } KisTemplateTree::~KisTemplateTree() { qDeleteAll(m_groups); } void KisTemplateTree::readTemplateTree() { readGroups(); readTemplates(); } void KisTemplateTree::writeTemplateTree() { QString localDir = KoResourcePaths::saveLocation("templates"); Q_FOREACH (KisTemplateGroup *group, m_groups) { //dbgUI <<"---------------------------------"; //dbgUI <<"group:" << group->name(); bool touched = false; QList templates = group->templates(); QList::iterator it = templates.begin(); for (; it != templates.end() && !touched && !group->touched(); ++it) touched = (*it)->touched(); if (group->touched() || touched) { //dbgUI <<"touched"; if (!group->isHidden()) { //dbgUI <<"not hidden"; QDir path; path.mkpath(localDir + group->name()); // create the local group dir } else { //dbgUI <<"hidden"; if (group->dirs().count() == 1 && group->dirs().contains(localDir)) { //dbgUI <<"local only"; QFile f(group->dirs().first()); f.remove(); //dbgUI <<"removing:" << group->dirs().first(); } else { //dbgUI <<"global"; QDir path; path.mkpath(localDir + group->name()); } } } Q_FOREACH (KisTemplate *t, templates) { if (t->touched()) { //dbgUI <<"++template:" << t->name(); writeTemplate(t, group, localDir); } if (t->isHidden() && t->touched()) { //dbgUI <<"+++ delete local template ##############"; writeTemplate(t, group, localDir); QFile::remove(t->file()); QFile::remove(t->picture()); } } } } -void KisTemplateTree::add(KisTemplateGroup *g) +bool KisTemplateTree::add(KisTemplateGroup *g) { KisTemplateGroup *group = find(g->name()); - if (group == 0) + if (group == 0) { m_groups.append(g); - else { - group->addDir(g->dirs().first()); // "...there can be only one..." (Queen) - delete g; - g = 0; + return true; } + + group->addDir(g->dirs().first()); // "...there can be only one..." (Queen) + delete g; + g = 0; + return false; } KisTemplateGroup *KisTemplateTree::find(const QString &name) const { QList::const_iterator it = m_groups.begin(); KisTemplateGroup* ret = 0; while (it != m_groups.end()) { if ((*it)->name() == name) { ret = *it; break; } ++it; } return ret; } void KisTemplateTree::readGroups() { QStringList dirs = KoResourcePaths::findDirs("templates"); Q_FOREACH (const QString & dirName, dirs) { if (!dirName.contains("templates")) continue; // Hack around broken KoResourcePaths QDir dir(dirName); // avoid the annoying warning if (!dir.exists()) continue; QStringList templateDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); Q_FOREACH (const QString & templateDirName, templateDirs) { QDir templateDir(dirName + "/" + templateDirName); QString name = templateDirName; QString defaultTab; int sortingWeight = 1000; if (templateDir.exists(".directory")) { KDesktopFile config(templateDir.absoluteFilePath(".directory")); KConfigGroup dg = config.desktopGroup(); name = dg.readEntry("Name"); defaultTab = dg.readEntry("X-KDE-DefaultTab"); sortingWeight = dg.readEntry("X-KDE-SortingWeight", 1000); } KisTemplateGroup *g = new KisTemplateGroup(name, templateDir.absolutePath() + QDir::separator(), sortingWeight); - add(g); - if (defaultTab == "true") - m_defaultGroup = g; + if (add(g)) { + if (defaultTab == "true") { + m_defaultGroup = g; + } + } } } } void KisTemplateTree::readTemplates() { QString dontShow = "imperial"; if ( QLocale().measurementSystem() == QLocale::ImperialSystem) { dontShow = "metric"; } Q_FOREACH (KisTemplateGroup* group, m_groups) { QStringList dirs = group->dirs(); for (QStringList::ConstIterator it = dirs.constBegin(); it != dirs.constEnd(); ++it) { QDir d(*it); if (!d.exists()) continue; QStringList files = d.entryList(QDir::Files | QDir::Readable, QDir::Name); for (int i = 0; i < files.count(); ++i) { QString filePath = *it + files[i]; //dbgUI <<"filePath:" << filePath; QString icon; QString text; QString description; QString hidden_str; QString fileName; bool hidden = false; bool defaultTemplate = false; QString templatePath; QString measureSystem; // If a desktop file, then read the name from it. // Otherwise (or if no name in it?) use file name if (KDesktopFile::isDesktopFile(filePath)) { KConfig _config(filePath, KConfig::SimpleConfig); KConfigGroup config(&_config, "Desktop Entry"); if (config.readEntry("Type") == "Link") { text = config.readEntry("Name"); fileName = filePath; description = config.readEntry("Comment"); //dbgUI <<"name:" << text; icon = config.readEntry("Icon"); if (icon[0] != '/' && // allow absolute paths for icons QFile::exists(*it + icon)) // allow icons from icontheme icon = *it + icon; //dbgUI <<"icon2:" << icon; hidden = config.readEntry("X-KDE-Hidden", false); defaultTemplate = config.readEntry("X-KDE-DefaultTemplate", false); measureSystem = config.readEntry("X-KDE-MeasureSystem").toLower(); // Don't add a template that is for the wrong measure system if (measureSystem == dontShow) continue; //dbgUI <<"hidden:" << hidden_str; templatePath = config.readPathEntry("URL", QString()); //dbgUI <<"Link to :" << templatePath; if (templatePath[0] != '/') { if (templatePath.left(6) == "file:/") // I doubt this will happen templatePath = templatePath.right(templatePath.length() - 6); //else // dbgUI <<"dirname=" << *it; templatePath = *it + templatePath; //dbgUI <<"templatePath:" << templatePath; } } else continue; // Invalid } // The else if and the else branch are here for compat. with the old system else if (files[i].right(4) != ".png") // Ignore everything that is not a PNG file continue; else { // Found a PNG file - the template must be here in the same dir. icon = filePath; QFileInfo fi(filePath); text = fi.completeBaseName(); templatePath = filePath; // Note that we store the .png file as the template ! // That's the way it's always been done. Then the app replaces the extension... } KisTemplate *t = new KisTemplate(text, description, templatePath, icon, fileName, measureSystem, hidden); group->add(t, false, false); // false -> we aren't a "user", false -> don't // "touch" the group to avoid useless // creation of dirs in .kde/blah/... if (defaultTemplate) m_defaultTemplate = t; } } } } void KisTemplateTree::writeTemplate(KisTemplate *t, KisTemplateGroup *group, const QString &localDir) { QString fileName; if (t->isHidden()) { fileName = t->fileName(); // try to remove the file if (QFile::remove(fileName) || !QFile::exists(fileName)) { QFile::remove(t->name()); QFile::remove(t->picture()); return; } } // be sure that the template's file name is unique so we don't overwrite an other QString const path = localDir + group->name() + '/'; QString const name = KisTemplates::trimmed(t->name()); fileName = path + name + ".desktop"; if (t->isHidden() && QFile::exists(fileName)) return; QString fill; while (QFile(fileName).exists()) { fill += '_'; fileName = path + fill + name + ".desktop"; } KConfig _config(fileName, KConfig::SimpleConfig); KConfigGroup config(&_config, "Desktop Entry"); config.writeEntry("Type", "Link"); config.writePathEntry("URL", t->file()); config.writeEntry("Name", t->name()); config.writeEntry("Icon", t->picture()); config.writeEntry("X-KDE-Hidden", t->isHidden()); } diff --git a/libs/ui/KisTemplateTree.h b/libs/ui/KisTemplateTree.h index 5b0d485e92..06e4b78fac 100644 --- a/libs/ui/KisTemplateTree.h +++ b/libs/ui/KisTemplateTree.h @@ -1,69 +1,69 @@ /* This file is part of the KDE project ompyright (C) 2000 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_TEMPLATE_TREE_H #define KIS_TEMPLATE_TREE_H #include #include #include "kritaui_export.h" class KisTemplate; class KisTemplateGroup; class KRITAUI_EXPORT KisTemplateTree { public: KisTemplateTree(const QString &templatesResourcePath, bool readTree = false); ~KisTemplateTree(); QString templatesResourcePath() const { return m_templatesResourcePath; } void readTemplateTree(); void writeTemplateTree(); - void add(KisTemplateGroup *g); + bool add(KisTemplateGroup *g); KisTemplateGroup *find(const QString &name) const; KisTemplateGroup *defaultGroup() const { return m_defaultGroup; } KisTemplate *defaultTemplate() const { return m_defaultTemplate; } QList groups () const { return m_groups; } private: void readGroups(); void readTemplates(); void writeTemplate(KisTemplate *t, KisTemplateGroup *group, const QString &localDir); QString m_templatesResourcePath; QList m_groups; KisTemplateGroup *m_defaultGroup; KisTemplate *m_defaultTemplate; }; #endif diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 28b13acafd..3399ebfe21 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1283 +1,1286 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_undo_adapter.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" #include #include "opengl/kis_opengl_canvas_debugger.h" #include "kis_algebra_2d.h" #include "kis_image_signal_router.h" #include "KisSnapPixelStrategy.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceProvider* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor canvasUpdateCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInImage = false; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup; KisSignalCompressor frameRenderStartCompressor; KisSignalCompressor regionOfInterestUpdateCompressor; QRect regionOfInterest; + qreal regionOfInterestMargin = 0.25; QRect renderingLimit; int isBatchUpdateActive = 0; bool effectiveLodAllowedInImage() { return lodAllowedInImage && !bootstrapLodBlocked; } void setActiveShapeManager(KoShapeManager *shapeManager); }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisSelectionSP selection; if (KisLayer *layer = dynamic_cast(node.data())) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } } else if (KisSelectionMask *mask = dynamic_cast(node.data())) { selection = mask->selection(); } if (!shapeManager && selection && selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0); shapeManager = shapeSelection->shapeManager(); } return shapeManager; } } KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceProvider *resourceManager, KisMainWindow *mainWindow, KisView *view, KoShapeControllerBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(mainWindow, SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); connect(mainWindow, SIGNAL(screenChanged()), SLOT(slotConfigChanged())); KisImageConfig config(false); m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit()); m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit()); m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); snapGuide()->overrideSnapStrategy(KoSnapGuide::PixelSnapping, new KisSnapPixelStrategy()); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInImage = cfg.levelOfDetailEnabled(); + m_d->regionOfInterestMargin = KisImageConfig(true).animationCacheRegionOfInterestMargin(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInImage); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), selectedShapesProxy(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start())); connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection())); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest())); connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg(true); const bool shouldShowDebugOverlay = (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) || cfg.enableBrushSpeedLogging(); if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); if (cfg.enableBrushSpeedLogging()) { connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) { m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag); disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget) { if (m_d->popupPalette) { m_d->popupPalette->setParent(widget->widget()); } if (m_d->canvasWidget != 0) { widget->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = widget; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->widget()->setAutoFillBackground(false); widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent); widget->widget()->setMouseTracking(true); widget->widget()->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller && controller->canvas() == this) { controller->changeCanvasWidget(widget->widget()); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager) { if (shapeManager != currentlyActiveShapeManager) { currentlyActiveShapeManager = shapeManager; selectedShapesProxy.setShapeManager(shapeManager); } } KoShapeManager* KisCanvas2::shapeManager() const { KoShapeManager *localShapeManager = this->localShapeManager(); // sanity check for consistency of the local shape manager KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) { localShapeManager = globalShapeManager(); } return localShapeManager ? localShapeManager : globalShapeManager(); } KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const { return &m_d->selectedShapesProxy; } KoShapeManager* KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } KoShapeManager *KisCanvas2::localShapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); if (localShapeManager != m_d->currentlyActiveShapeManager) { m_d->setActiveShapeManager(localShapeManager); } return localShapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const { return m_d->inputActionGroupsMask; } void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask) { m_d->inputActionGroupsMask = mask; } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg(true); m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg(true); QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL()); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL && !KisOpenGL::hasOpenGL()) { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; useOpenGL = false; } m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL); if (useOpenGL) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false); createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageSP image = m_d->view->image(); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged())); connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged())); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); setLodAllowedInCanvas(m_d->lodAllowedInImage); emit sigCanvasEngineChanged(); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg(true); bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { /** * We don't do patched loading for openGL canvas, becasue it loads * the tiles, which are bascially "patches". Therefore, big chunks * of memory are never allocated. */ if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } void KisCanvas2::slotImageColorSpaceChanged() { KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); image->barrierLock(); m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace()); image->unlock(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { KisImageConfig cfg(false); m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof); if (softProof) { conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck); } } m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { auto tryIssueCanvasUpdates = [this](const QRect &vRect) { if (!m_d->isBatchUpdateActive) { // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { m_d->savedUpdateRect = QRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) { m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } } }; auto uploadData = [this, tryIssueCanvasUpdates](const QVector &infoObjects) { QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects); const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(), QRect(), std::bit_or()); tryIssueCanvasUpdates(vRect); }; bool shouldExplicitlyIssueUpdates = false; QVector infoObjects; KisUpdateInfoList originalInfoObjects; m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects); for (auto it = originalInfoObjects.constBegin(); it != originalInfoObjects.constEnd(); ++it) { KisUpdateInfoSP info = *it; const KisMarkerUpdateInfo *batchInfo = dynamic_cast(info.data()); if (batchInfo) { if (!infoObjects.isEmpty()) { uploadData(infoObjects); infoObjects.clear(); } if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) { m_d->isBatchUpdateActive++; } else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) { m_d->isBatchUpdateActive--; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0); if (m_d->isBatchUpdateActive == 0) { shouldExplicitlyIssueUpdates = true; } } else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(true); } else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(false); shouldExplicitlyIssueUpdates = true; } } else { infoObjects << info; } } if (!infoObjects.isEmpty()) { uploadData(infoObjects); } else if (shouldExplicitlyIssueUpdates) { tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels()); } } void KisCanvas2::slotBeginUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotEndUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotSetLodUpdatesBlocked(bool value) { KisUpdateInfoSP info = new KisMarkerUpdateInfo(value ? KisMarkerUpdateInfo::BlockLodUpdates : KisMarkerUpdateInfo::UnblockLodUpdates, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotDoCanvasUpdate() { /** * WARNING: in isBusy() we access openGL functions without making the painting * context current. We hope that currently active context will be Qt's one, * which is shared with our own. */ if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->canvasUpdateCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->canvasUpdateCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction m_d->regionOfInterestUpdateCompressor.start(); } QRect KisCanvas2::regionOfInterest() const { return m_d->regionOfInterest; } void KisCanvas2::slotUpdateRegionOfInterest() { const QRect oldRegionOfInterest = m_d->regionOfInterest; - const qreal ratio = 0.25; + const qreal ratio = m_d->regionOfInterestMargin; const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect(); const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels(); - m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect; + m_d->regionOfInterest = proposedRoi & imageRect; if (m_d->regionOfInterest != oldRegionOfInterest) { emit sigRegionOfInterestChanged(m_d->regionOfInterest); } } void KisCanvas2::slotReferenceImagesChanged() { canvasController()->resetScrollBars(); } void KisCanvas2::setRenderingLimit(const QRect &rc) { m_d->renderingLimit = rc; } QRect KisCanvas2::renderingLimit() const { return m_d->renderingLimit; } void KisCanvas2::slotTrySwitchShapeManager() { KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); m_d->setActiveShapeManager(newManager); } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInImage()) return; const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg(true); const int maxLod = cfg.numMipmapLevels(); const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); if (m_d->effectiveLodAllowedInImage()) { KisImageSP image = this->image(); image->setDesiredLevelOfDetail(lod); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); // The given offset is in widget logical pixels. In order to prevent fuzzy // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio // is not integral, we adjusts the offset to map to whole device pixels. // // FIXME: This is a temporary hack for fixing the canvas under fractional // DPI scaling before a new coordinate system is introduced. QPointF offsetAdjusted = m_d->coordinatesConverter->snapToDevicePixel(documentOffset); m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); m_d->regionOfInterestUpdateCompressor.start(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); + m_d->regionOfInterestMargin = KisImageConfig(true).animationCacheRegionOfInterestMargin(); resetCanvas(cfg.useOpenGL()); // HACK: Sometimes screenNumber(this->canvasWidget()) is not able to get the // proper screenNumber when moving the window across screens. Using // the coordinates should be able to work around this. // FIXME: We should change to associate the display profiles with the screen // model and serial number instead. See https://bugs.kde.org/show_bug.cgi?id=407498 QScreen *canvasScreen = this->canvasWidget()->window()->windowHandle()->screen(); QPoint canvasScreenCenter = canvasScreen->geometry().center(); int canvasScreenNumber = QApplication::desktop()->screenNumber(canvasScreenCenter); if (canvasScreenNumber == -1) { // Fall back to the old way of getting the screenNumber canvasScreenNumber = QApplication::desktop()->screenNumber(this->canvasWidget()); } if (canvasScreenNumber != -1) { setDisplayProfile(cfg.displayProfile(canvasScreenNumber)); } else { warnUI << "Failed to get screenNumber for updating display profile."; } initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::setDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayColorConverter(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInImage); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInImage = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage()); } notifyLevelOfDetailChange(); KisConfig cfg(false); cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage); KisUsageLogger::log(QString("Instant Preview Setting: %1").arg(m_d->lodAllowedInImage)); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInImage; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const { KisCanvasDecorationSP deco = decoration("referenceImagesDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index 8e5b209c97..fa9e787de6 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,386 +1,384 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_canvas_controller.h" #include #include #include #include #include #include "kis_canvas_decoration.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "opengl/kis_opengl_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_config.h" #include "kis_signal_compressor_with_param.h" #include "kis_config_notifier.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) : q(qq) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KoCanvasSupervisor *observerProvider, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, observerProvider, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { if (canvas) { - KisCanvas2 *kritaCanvas = dynamic_cast(canvas); - KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas); - + KisCanvas2 *kritaCanvas = qobject_cast(canvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); } else { m_d->coordinatesConverter = 0; } KoCanvasControllerWidget::setCanvas(canvas); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } QPointF KisCanvasController::currentCursorPosition() const { KoCanvasBase *canvas = m_d->view->canvasBase(); QWidget *canvasWidget = canvas->canvasWidget(); const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos()); return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); } else if (event->type() == QEvent::FocusIn) { m_d->view->syncLastActiveNodeToDocument(); } return false; } void KisCanvasController::updateDocumentSize(const QSizeF &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle, const QPointF ¢er) { QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvas(qreal angle) { rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint()); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } qreal KisCanvasController::rotation() const { return m_d->coordinatesConverter->rotationAngle(); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotTogglePixelGrid(bool value) { KisConfig cfg(false); cfg.enablePixelGrid(value); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); bool result = levelOfDetailMode(); if (!value || result) { m_d->view->viewManager()->showFloatingMessage( i18n("Instant Preview Mode: %1", result ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } else { QString reason; if (!kritaCanvas->canvasIsOpenGL()) { reason = i18n("Instant Preview is only supported with OpenGL activated"); } else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { QString filteringMode = kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? i18n("Bilinear") : i18n("Nearest Neighbour"); reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode); } m_d->view->viewManager()->showFloatingMessage( i18n("Failed activating Instant Preview mode!\n\n%1", reason), QIcon(), 5000, KisFloatingMessage::Low); } } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } void KisCanvasController::saveCanvasState(KisPropertiesConfiguration &config) const { const QPointF ¢er = preferredCenter(); config.setProperty("panX", center.x()); config.setProperty("panY", center.y()); config.setProperty("rotation", rotation()); config.setProperty("mirror", m_d->coordinatesConverter->xAxisMirrored()); config.setProperty("wrapAround", wrapAroundMode()); config.setProperty("enableInstantPreview", levelOfDetailMode()); } void KisCanvasController::restoreCanvasState(const KisPropertiesConfiguration &config) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); mirrorCanvas(config.getBool("mirror", false)); rotateCanvas(config.getFloat("rotation", 0.0f)); const QPointF ¢er = preferredCenter(); float panX = config.getFloat("panX", center.x()); float panY = config.getFloat("panY", center.y()); setPreferredCenter(QPointF(panX, panY)); slotToggleWrapAroundMode(config.getBool("wrapAround", false)); kritaCanvas->setLodAllowedInCanvas(config.getBool("enableInstantPreview", false)); } void KisCanvasController::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. KisDocument *doc = m_d->view->document(); if (!doc) return; QRectF documentBounds = doc->documentBounds(); QRectF viewRect = m_d->coordinatesConverter->imageToWidget(documentBounds); // Cancel out any existing pan const QRectF imageBounds = m_d->view->image()->bounds(); const QRectF imageBB = m_d->coordinatesConverter->imageToWidget(imageBounds); QPointF pan = imageBB.topLeft(); viewRect.translate(-pan); int drawH = viewport()->height(); int drawW = viewport()->width(); qreal horizontalReserve = vastScrollingFactor() * drawW; qreal verticalReserve = vastScrollingFactor() * drawH; qreal xMin = viewRect.left() - horizontalReserve; qreal yMin = viewRect.top() - verticalReserve; qreal xMax = viewRect.right() - drawW + horizontalReserve; qreal yMax = viewRect.bottom() - drawH + verticalReserve; QScrollBar *hScroll = horizontalScrollBar(); QScrollBar *vScroll = verticalScrollBar(); hScroll->setRange(static_cast(xMin), static_cast(xMax)); vScroll->setRange(static_cast(yMin), static_cast(yMax)); int fontHeight = QFontMetrics(font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontHeight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontHeight); } diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 7c7ff72879..2dcc4a0f7b 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1046 +1,1055 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * 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. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) #include #endif #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; bool displayShaderCompiledWithDisplayFilterSupport{false}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; QOpenGLBuffer lineBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; QColor cursorColor; bool lodSwitchInProgress = false; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg(false); cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); + connect(d->openGLImageTextures.data(), + SIGNAL(sigShowFloatingMessage(QString, int, bool)), + SLOT(slotShowFloatingMessage(QString, int, bool))); + setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_MACOS setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_DontCreateNativeAncestors, true); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // we should make sure the texture doesn't have alpha channel, // otherwise blending will not work correctly. if (KisOpenGLModeProber::instance()->useHDRMode()) { setTextureFormat(GL_RGBA16F); } else { /** * When in pure OpenGL mode, the canvas surface will have alpha * channel. Therefore, if our canvas blending algorithm produces * semi-transparent pixels (and it does), then Krita window itself * will become transparent. Which is not good. * * In Angle mode, GL_RGB8 is not available (and the transparence effect * doesn't exist at all). */ if (!KisOpenGL::hasOpenGLES()) { setTextureFormat(GL_RGB8); } } #endif setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged())); slotConfigChanged(); slotPixelGridModeChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::notifyImageColorSpaceChanged(const KoColorSpace *cs) { // FIXME: on color space change the data is refetched multiple // times by different actors! if (d->openGLImageTextures->setImageColorSpace(cs)) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } 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()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) if (!KisOpenGL::hasOpenGLES()) { d->glFn201 = context()->versionFunctions(); if (!d->glFn201) { warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; } } else { d->glFn201 = nullptr; } #endif KisConfig cfg(true); d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineBuffer.create(); d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); delete d->checkerShader; delete d->solidColorShader; d->checkerShader = 0; d->solidColorShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } initializeDisplayShader(); } void KisOpenGLCanvas2::initializeDisplayShader() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; delete d->displayShader; d->displayShader = 0; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg(false); qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context), QMessageBox::Close); cfg.disableOpenGL(); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int /*width*/, int /*height*/) { // The given size is the widget size but here we actually want to give // KisCoordinatesConverter the viewport size aligned to device pixels. coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); #ifndef Q_OS_MACOS if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } #else glLogicOp(GL_XOR); #endif // Q_OS_OSX #else // HAS_ONLY_OPENGL_ES KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif // HAS_ONLY_OPENGL_ES } else { glEnable(GL_BLEND); glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE, GL_ONE); } d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int i = 0; i < subPathPolygons.size(); i++) { const QPolygonF& polygon = subPathPolygons.at(i); QVector vertices; vertices.resize(polygon.count()); for (int j = 0; j < polygon.count(); j++) { QPointF p = polygon.at(j); vertices[j].setX(p.x()); vertices[j].setY(p.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); } glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glDisable(GL_COLOR_LOGIC_OP); #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { glDisable(GL_BLEND); } d->solidColorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::setLodResetInProgress(bool value) { d->lodSwitchInProgress = value; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); viewportRect &= vrect; } converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); glDisable(GL_BLEND); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); if (!renderingLimit.isEmpty()) { widgetRectInImagePixels &= renderingLimit; } qreal scaleX, scaleY; converter->imagePhysicalScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect; QRectF modelRect; if (renderingLimit.isEmpty()) { textureRect = tile->tileRectInTexturePixels(); modelRect = tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } else { const QRect limitedTileRect = tile->tileRectInImagePixels() & renderingLimit; textureRect = tile->imageRectInTexturePixels(limitedTileRect); modelRect = limitedTileRect.translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } glActiveTexture(GL_TEXTURE0); const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisable(GL_BLEND); } QSize KisOpenGLCanvas2::viewportDevicePixelSize() const { // This is how QOpenGLCanvas sets the FBO and the viewport size. If // devicePixelRatioF() is non-integral, the result is truncated. int viewportWidth = static_cast(width() * devicePixelRatioF()); int viewportHeight = static_cast(height() * devicePixelRatioF()); return QSize(viewportWidth, viewportHeight); } QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const { QSize viewportSize = viewportDevicePixelSize(); qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); return QSizeF(scaledWidth, scaledHeight); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true); d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); d->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } void KisOpenGLCanvas2::slotPixelGridModeChanged() { KisConfig cfg(true); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); update(); } +void KisOpenGLCanvas2::slotShowFloatingMessage(const QString &message, int timeout, bool priority) +{ + canvas()->imageView()->showFloatingMessage(message, QIcon(), timeout, priority ? KisFloatingMessage::High : KisFloatingMessage::Medium); +} + QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); KoColor convertedBackgroudColor = canvas()->displayColorConverter()->applyDisplayFiltering( KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()), Float32BitsColorDepthID); const float *pixel = reinterpret_cast(convertedBackgroudColor.data()); glClearColor(pixel[0], pixel[1], pixel[2], 1.0); } glClear(GL_COLOR_BUFFER_BIT); if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); d->canvasInitialized = false; // TODO: check if actually needed? initializeDisplayShader(); d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayColorConverter(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image()); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info, d->lodSwitchInProgress); } return QRect(); // FIXME: Implement dirty rect for OpenGL } QVector KisOpenGLCanvas2::updateCanvasProjection(const QVector &infoObjects) { #ifdef Q_OS_MACOS /** * On OSX openGL defferent (shared) contexts have different execution queues. * It means that the textures uploading and their painting can be easily reordered. * To overcome the issue, we should ensure that the textures are uploaded in the * same openGL context as the painting is done. */ QOpenGLContext *oldContext = QOpenGLContext::currentContext(); QSurface *oldSurface = oldContext ? oldContext->surface() : 0; this->makeCurrent(); #endif QVector result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects); #ifdef Q_OS_MACOS if (oldContext) { oldContext->makeCurrent(oldSurface); } else { this->doneCurrent(); } #endif return result; } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h index 595b2073f6..fc08867b5f 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -1,132 +1,135 @@ /* * Copyright (C) Boudewijn Rempt , (C) 2006 * Copyright (C) Michael Abrahams , (C) 2015 * * 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_OPENGL_CANVAS_2_H #define KIS_OPENGL_CANVAS_2_H #include #ifndef Q_OS_MACOS #include #else #include #endif #include "canvas/kis_canvas_widget_base.h" #include "opengl/kis_opengl_image_textures.h" #include "kritaui_export.h" #include "kis_ui_types.h" class KisCanvas2; class KisDisplayColorConverter; class QOpenGLShaderProgram; class QPainterPath; #ifndef Q_MOC_RUN #ifndef Q_OS_MACOS #define GLFunctions QOpenGLFunctions #else #define GLFunctions QOpenGLFunctions_3_2_Core #endif #endif /** * KisOpenGLCanvas is the widget that shows the actual image using OpenGL * * NOTE: if you change something in the event handling here, also change it * in the qpainter canvas. * */ class KRITAUI_EXPORT KisOpenGLCanvas2 : public QOpenGLWidget #ifndef Q_MOC_RUN , protected GLFunctions #endif , public KisCanvasWidgetBase { Q_OBJECT public: KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter); ~KisOpenGLCanvas2() override; public: // QOpenGLWidget void resizeGL(int width, int height) override; void initializeGL() override; void paintGL() override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; void inputMethodEvent(QInputMethodEvent *event) override; public: void renderCanvasGL(); void renderDecorations(QPainter *painter); void paintToolOutline(const QPainterPath &path); public: // Implement kis_abstract_canvas_widget interface void setDisplayFilter(QSharedPointer displayFilter) override; void notifyImageColorSpaceChanged(const KoColorSpace *cs) override; void setWrapAroundViewingMode(bool value) override; void channelSelectionChanged(const QBitArray &channelFlags) override; void setDisplayColorConverter(KisDisplayColorConverter *colorConverter) override; void finishResizingImage(qint32 w, qint32 h) override; KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) override; QRect updateCanvasProjection(KisUpdateInfoSP info) override; QVector updateCanvasProjection(const QVector &infoObjects) override; QWidget *widget() override { return this; } bool isBusy() const override; void setLodResetInProgress(bool value) override; void setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing); KisOpenGLImageTexturesSP openGLImageTextures() const; public Q_SLOTS: void slotConfigChanged(); void slotPixelGridModeChanged(); +private Q_SLOTS: + void slotShowFloatingMessage(const QString &message, int timeout, bool priority); + protected: // KisCanvasWidgetBase bool callFocusNextPrevChild(bool next) override; private: void initializeShaders(); void initializeDisplayShader(); void reportFailedShaderCompilation(const QString &context); void drawImage(); void drawCheckers(); void drawGrid(); QSize viewportDevicePixelSize() const; QSizeF widgetSizeAlignedToDevicePixel() const; private: struct Private; Private * const d; }; #endif // KIS_OPENGL_CANVAS_2_H diff --git a/libs/ui/opengl/kis_opengl_image_textures.cpp b/libs/ui/opengl/kis_opengl_image_textures.cpp index 79cc60728e..236af96a57 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.cpp +++ b/libs/ui/opengl/kis_opengl_image_textures.cpp @@ -1,659 +1,655 @@ /* * Copyright (c) 2005-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 "opengl/kis_opengl_image_textures.h" #ifdef HAS_ONLY_OPENGL_ES #include #endif #ifndef HAS_ONLY_OPENGL_ES #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_config.h" #include "KisPart.h" #include "KisOpenGLModeProber.h" #ifdef HAVE_OPENEXR #include #endif #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif // GL_EXT_texture_format_BGRA8888 #ifndef GL_BGRA_EXT #define GL_BGRA_EXT 0x80E1 #endif #ifndef GL_BGRA8_EXT #define GL_BGRA8_EXT 0x93A1 #endif KisOpenGLImageTextures::ImageTexturesMap KisOpenGLImageTextures::imageTexturesMap; KisOpenGLImageTextures::KisOpenGLImageTextures() : m_image(0) , m_monitorProfile(0) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_useOcio(false) , m_initialized(false) { KisConfig cfg(true); m_renderingIntent = (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent(); m_conversionFlags = KoColorConversionTransformation::HighQuality; if (cfg.useBlackPointCompensation()) m_conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; if (!cfg.allowLCMSOptimization()) m_conversionFlags |= KoColorConversionTransformation::NoOptimization; m_useOcio = cfg.useOcio(); } KisOpenGLImageTextures::KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : m_image(image) , m_monitorProfile(monitorProfile) , m_renderingIntent(renderingIntent) , m_conversionFlags(conversionFlags) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_useOcio(false) , m_initialized(false) { Q_ASSERT(renderingIntent < 4); } void KisOpenGLImageTextures::initGL(QOpenGLFunctions *f) { if (f) { m_glFuncs = f; } else { errUI << "Tried to create OpenGLImageTextures with uninitialized QOpenGLFunctions"; } getTextureSize(&m_texturesInfo); // we use local static object for creating pools shared among // different images static KisTextureTileInfoPoolRegistry s_poolRegistry; m_updateInfoBuilder.setTextureInfoPool(s_poolRegistry.getPool(m_texturesInfo.width, m_texturesInfo.height)); m_glFuncs->glGenTextures(1, &m_checkerTexture); recreateImageTextureTiles(); KisOpenGLUpdateInfoSP info = updateCache(m_image->bounds(), m_image); recalculateCache(info, false); } KisOpenGLImageTextures::~KisOpenGLImageTextures() { ImageTexturesMap::iterator it = imageTexturesMap.find(m_image); if (it != imageTexturesMap.end()) { KisOpenGLImageTextures *textures = it.value(); if (textures == this) { dbgUI << "Removing shared image context from map"; imageTexturesMap.erase(it); } } destroyImageTextureTiles(); m_glFuncs->glDeleteTextures(1, &m_checkerTexture); } KisImageSP KisOpenGLImageTextures::image() const { return m_image; } bool KisOpenGLImageTextures::imageCanShareTextures() { KisConfig cfg(true); if (cfg.useOcio()) return false; if (KisPart::instance()->mainwindowCount() == 1) return true; if (qApp->desktop()->screenCount() == 1) return true; for (int i = 1; i < qApp->desktop()->screenCount(); i++) { if (cfg.displayProfile(i) != cfg.displayProfile(i - 1)) { return false; } } return true; } KisOpenGLImageTexturesSP KisOpenGLImageTextures::getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { // Disabled until we figure out why we're deleting the shared textures on closing the second view on a single image if (false && imageCanShareTextures()) { ImageTexturesMap::iterator it = imageTexturesMap.find(image); if (it != imageTexturesMap.end()) { KisOpenGLImageTexturesSP textures = it.value(); textures->setMonitorProfile(monitorProfile, renderingIntent, conversionFlags); return textures; } else { KisOpenGLImageTextures *imageTextures = new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); imageTexturesMap[image] = imageTextures; dbgUI << "Added shareable textures to map"; return imageTextures; } } else { return new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); } } void KisOpenGLImageTextures::recreateImageTextureTiles() { destroyImageTextureTiles(); updateTextureFormat(); const KoColorSpace *tilesDestinationColorSpace = m_updateInfoBuilder.destinationColorSpace(); if (!tilesDestinationColorSpace) { qDebug() << "No destination colorspace!!!!"; return; } m_storedImageBounds = m_image->bounds(); const int lastCol = xToCol(m_image->width()); const int lastRow = yToRow(m_image->height()); m_numCols = lastCol + 1; // Default color is transparent black const int pixelSize = tilesDestinationColorSpace->pixelSize(); QByteArray emptyTileData((m_texturesInfo.width) * (m_texturesInfo.height) * pixelSize, 0); KisConfig config(true); KisOpenGL::FilterMode mode = (KisOpenGL::FilterMode)config.openGLFilteringMode(); QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); m_initialized = true; dbgUI << "OpenGL: creating texture tiles of size" << m_texturesInfo.height << "x" << m_texturesInfo.width; m_textureTiles.reserve((lastRow+1)*m_numCols); for (int row = 0; row <= lastRow; row++) { for (int col = 0; col <= lastCol; col++) { QRect tileRect = m_updateInfoBuilder.calculateEffectiveTileRect(col, row, m_image->bounds()); KisTextureTile *tile = new KisTextureTile(tileRect, &m_texturesInfo, emptyTileData, mode, config.useOpenGLTextureBuffer(), config.numMipmapLevels(), f); m_textureTiles.append(tile); } } } else { dbgUI << "Tried to init texture tiles without a current OpenGL Context."; } } void KisOpenGLImageTextures::destroyImageTextureTiles() { if (m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { delete tile; } m_textureTiles.clear(); m_storedImageBounds = QRect(); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCache(const QRect& rect, KisImageSP srcImage) { return updateCacheImpl(rect, srcImage, true); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheNoConversion(const QRect& rect) { return updateCacheImpl(rect, m_image, false); } // TODO: add sanity checks about the conformance of the passed srcImage! KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheImpl(const QRect& rect, KisImageSP srcImage, bool convertColorSpace) { if (!m_initialized) return new KisOpenGLUpdateInfo(); return m_updateInfoBuilder.buildUpdateInfo(rect, srcImage, convertColorSpace); } void KisOpenGLImageTextures::recalculateCache(KisUpdateInfoSP info, bool blockMipmapRegeneration) { if (!m_initialized) { dbgUI << "OpenGL: Tried to edit image texture cache before it was initialized."; return; } KisOpenGLUpdateInfoSP glInfo = dynamic_cast(info.data()); if(!glInfo) return; KisTextureTileUpdateInfoSP tileInfo; Q_FOREACH (tileInfo, glInfo->tileList) { KisTextureTile *tile = getTextureTileCR(tileInfo->tileCol(), tileInfo->tileRow()); KIS_ASSERT_RECOVER_RETURN(tile); tile->update(*tileInfo, blockMipmapRegeneration); } } void KisOpenGLImageTextures::generateCheckerTexture(const QImage &checkImage) { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); dbgUI << "Attaching checker texture" << checkerTexture(); f->glBindTexture(GL_TEXTURE_2D, checkerTexture()); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); QImage img = checkImage; if (checkImage.width() != BACKGROUND_TEXTURE_SIZE || checkImage.height() != BACKGROUND_TEXTURE_SIZE) { img = checkImage.scaled(BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE); } #ifdef HAS_ONLY_OPENGL_ES GLint format = GL_RGBA, internalFormat = GL_RGBA8; #else GLint format = GL_BGRA, internalFormat = GL_RGBA8; if (KisOpenGL::hasOpenGLES()) { if (ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_format_BGRA8888"))) { format = GL_BGRA_EXT; internalFormat = GL_BGRA8_EXT; } else { format = GL_RGBA; } } #endif f->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE, 0, format, GL_UNSIGNED_BYTE, img.constBits()); } else { dbgUI << "OpenGL: Tried to generate checker texture before OpenGL was initialized."; } } GLuint KisOpenGLImageTextures::checkerTexture() { if (m_glFuncs) { if (m_checkerTexture == 0) { m_glFuncs->glGenTextures(1, &m_checkerTexture); } return m_checkerTexture; } else { dbgUI << "Tried to access checker texture before OpenGL was initialized"; return 0; } } void KisOpenGLImageTextures::updateConfig(bool useBuffer, int NumMipmapLevels) { if(m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { tile->setUseBuffer(useBuffer); tile->setNumMipmapLevels(NumMipmapLevels); } } void KisOpenGLImageTextures::slotImageSizeChanged(qint32 /*w*/, qint32 /*h*/) { recreateImageTextureTiles(); } KisOpenGLUpdateInfoBuilder &KisOpenGLImageTextures::updateInfoBuilder() { return m_updateInfoBuilder; } void KisOpenGLImageTextures::setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgUI << "Setting monitor profile to" << monitorProfile->name() << renderingIntent << conversionFlags; m_monitorProfile = monitorProfile; m_renderingIntent = renderingIntent; m_conversionFlags = conversionFlags; recreateImageTextureTiles(); } bool KisOpenGLImageTextures::setImageColorSpace(const KoColorSpace *cs) { Q_UNUSED(cs); // TODO: implement lazy update: do not re-upload textures when not needed recreateImageTextureTiles(); return true; } void KisOpenGLImageTextures::setChannelFlags(const QBitArray &channelFlags) { QBitArray targetChannelFlags = channelFlags; int selectedChannels = 0; const KoColorSpace *projectionCs = m_image->projection()->colorSpace(); QList channelInfo = projectionCs->channels(); if (targetChannelFlags.size() != channelInfo.size()) { targetChannelFlags = QBitArray(); } int selectedChannelIndex = -1; for (int i = 0; i < targetChannelFlags.size(); ++i) { if (targetChannelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) { selectedChannels++; selectedChannelIndex = i; } } const bool allChannelsSelected = (selectedChannels == targetChannelFlags.size()); const bool onlyOneChannelSelected = (selectedChannels == 1); // OCIO has its own channel swizzling if (allChannelsSelected || m_useOcio) { m_updateInfoBuilder.setChannelFlags(QBitArray(), false, -1); } else { m_updateInfoBuilder.setChannelFlags(targetChannelFlags, onlyOneChannelSelected, selectedChannelIndex); } } void KisOpenGLImageTextures::setProofingConfig(KisProofingConfigurationSP proofingConfig) { m_updateInfoBuilder.setProofingConfig(proofingConfig); } void KisOpenGLImageTextures::getTextureSize(KisGLTexturesInfo *texturesInfo) { KisConfig cfg(true); const GLint preferredTextureSize = cfg.openGLTextureSize(); GLint maxTextureSize; if (m_glFuncs) { m_glFuncs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } else { dbgUI << "OpenGL: Tried to read texture size before OpenGL was initialized."; maxTextureSize = GL_MAX_TEXTURE_SIZE; } texturesInfo->width = qMin(preferredTextureSize, maxTextureSize); texturesInfo->height = qMin(preferredTextureSize, maxTextureSize); texturesInfo->border = cfg.textureOverlapBorder(); texturesInfo->effectiveWidth = texturesInfo->width - 2 * texturesInfo->border; texturesInfo->effectiveHeight = texturesInfo->height - 2 * texturesInfo->border; m_updateInfoBuilder.setTextureBorder(texturesInfo->border); m_updateInfoBuilder.setEffectiveTextureSize( QSize(texturesInfo->effectiveWidth, texturesInfo->effectiveHeight)); } bool KisOpenGLImageTextures::internalColorManagementActive() const { return m_internalColorManagementActive; } bool KisOpenGLImageTextures::setInternalColorManagementActive(bool value) { bool needsFinalRegeneration = m_internalColorManagementActive != value; if (needsFinalRegeneration) { m_internalColorManagementActive = value; recreateImageTextureTiles(); // at this point the value of m_internalColorManagementActive might // have been forcely reverted to 'false' in case of some problems } return needsFinalRegeneration; } namespace { void initializeRGBA16FTextures(QOpenGLContext *ctx, KisGLTexturesInfo &texturesInfo, KoID &destinationColorDepthId) { if (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3()) { texturesInfo.internalFormat = GL_RGBA16F; dbgUI << "Using half (GLES or GL3)"; } else if (ctx->hasExtension("GL_ARB_texture_float")) { #ifndef HAS_ONLY_OPENGL_ES texturesInfo.internalFormat = GL_RGBA16F_ARB; dbgUI << "Using ARB half"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { texturesInfo.internalFormat = GL_RGBA_FLOAT16_ATI; dbgUI << "Using ATI half"; #else KIS_ASSERT_X(false, "initializeRGBA16FTextures", "Unexpected KisOpenGL::hasOpenGLES and \ KisOpenGL::hasOpenGL3 returned false"); #endif } bool haveBuiltInOpenExr = false; #ifdef HAVE_OPENEXR haveBuiltInOpenExr = true; #endif if (haveBuiltInOpenExr && (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3())) { texturesInfo.type = GL_HALF_FLOAT; destinationColorDepthId = Float16BitsColorDepthID; dbgUI << "Pixel type half (GLES or GL3)"; } else if (haveBuiltInOpenExr && ctx->hasExtension("GL_ARB_half_float_pixel")) { #ifndef HAS_ONLY_OPENGL_ES texturesInfo.type = GL_HALF_FLOAT_ARB; destinationColorDepthId = Float16BitsColorDepthID; dbgUI << "Pixel type half"; } else { texturesInfo.type = GL_FLOAT; destinationColorDepthId = Float32BitsColorDepthID; dbgUI << "Pixel type float"; #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES and \ KisOpenGL::hasOpenGL3 returned false"); #endif } texturesInfo.format = GL_RGBA; } } void KisOpenGLImageTextures::updateTextureFormat() { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (!(m_image && ctx)) return; if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_BGRA; #else KIS_ASSERT_X(false, "KisOpenGLImageTextures::updateTextureFormat", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { #ifdef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_RGBA; #else m_texturesInfo.internalFormat = GL_BGRA8_EXT; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_BGRA_EXT; if(!ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_format_BGRA8888"))) { // The red and blue channels are swapped, but it will be re-swapped // by texture swizzle mask set in KisTextureTile::setTextureParameters m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_RGBA; } #endif } const bool useHDRMode = KisOpenGLModeProber::instance()->useHDRMode(); const KoID colorModelId = m_image->colorSpace()->colorModelId(); const KoID colorDepthId = useHDRMode ? Float16BitsColorDepthID : m_image->colorSpace()->colorDepthId(); KoID destinationColorModelId = RGBAColorModelID; KoID destinationColorDepthId = Integer8BitsColorDepthID; dbgUI << "Choosing texture format:"; if (colorModelId == RGBAColorModelID) { if (colorDepthId == Float16BitsColorDepthID) { initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId); } else if (colorDepthId == Float32BitsColorDepthID) { if (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3()) { m_texturesInfo.internalFormat = GL_RGBA32F; dbgUI << "Using float (GLES or GL3)"; } else if (ctx->hasExtension("GL_ARB_texture_float")) { #ifndef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA32F_ARB; dbgUI << "Using ARB float"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA_FLOAT32_ATI; dbgUI << "Using ATI float"; #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::updateTextureFormat", "Unexpected KisOpenGL::hasOpenGLES and \ KisOpenGL::hasOpenGL3 returned false"); #endif } m_texturesInfo.type = GL_FLOAT; m_texturesInfo.format = GL_RGBA; destinationColorDepthId = Float32BitsColorDepthID; } else if (colorDepthId == Integer16BitsColorDepthID) { #ifndef HAS_ONLY_OPENGL_ES if (!KisOpenGL::hasOpenGLES()) { m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using 16 bits rgba"; } #endif // TODO: for ANGLE, see if we can convert to 16f to support 10-bit display } } else { // We will convert the colorspace to 16 bits rgba, instead of 8 bits if (colorDepthId == Integer16BitsColorDepthID && !KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using conversion to 16 bits rgba"; #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::updateTextureFormat", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else if (colorDepthId == Float16BitsColorDepthID && KisOpenGL::hasOpenGLES()) { // TODO: try removing opengl es limit initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId); } // TODO: for ANGLE, see if we can convert to 16f to support 10-bit display } if (!m_internalColorManagementActive && colorModelId != destinationColorModelId) { KisConfig cfg(false); KisConfig::OcioColorManagementMode cm = cfg.ocioColorManagementMode(); if (cm != KisConfig::INTERNAL) { - QMessageBox::critical(0, - i18nc("@title:window", "Krita"), - i18n("You enabled OpenColorIO based color management, but your image is not an RGB image.\n" - "OpenColorIO-based color management only works with RGB images.\n" - "Please check the settings in the LUT docker.\n" - "OpenColorIO will now be deactivated.")); + emit sigShowFloatingMessage( + i18n("OpenColorIO is disabled: image color space is not supported"), 5000, true); } warnUI << "WARNING: Internal color management was forcibly enabled"; warnUI << "Color Management Mode: " << cm; warnUI << ppVar(m_image->colorSpace()); warnUI << ppVar(destinationColorModelId); warnUI << ppVar(destinationColorDepthId); cfg.setOcioColorManagementMode(KisConfig::INTERNAL); m_internalColorManagementActive = true; } const KoColorProfile *profile = m_internalColorManagementActive || colorModelId != destinationColorModelId ? m_monitorProfile : m_image->colorSpace()->profile(); /** * TODO: add an optimization so that the tile->convertTo() method * would not be called when not needed (DK) */ const KoColorSpace *tilesDestinationColorSpace = KoColorSpaceRegistry::instance()->colorSpace(destinationColorModelId.id(), destinationColorDepthId.id(), profile); m_updateInfoBuilder.setConversionOptions( ConversionOptions(tilesDestinationColorSpace, m_renderingIntent, m_conversionFlags)); } diff --git a/libs/ui/opengl/kis_opengl_image_textures.h b/libs/ui/opengl/kis_opengl_image_textures.h index c8d4e55d8b..3ce622caba 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.h +++ b/libs/ui/opengl/kis_opengl_image_textures.h @@ -1,207 +1,212 @@ /* * Copyright (c) 2005-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. */ #ifndef KIS_OPENGL_IMAGE_TEXTURES_H_ #define KIS_OPENGL_IMAGE_TEXTURES_H_ #include #include #include #include "kritaui_export.h" #include "kis_shared.h" #include "canvas/kis_update_info.h" #include "opengl/kis_texture_tile.h" #include "KisOpenGLUpdateInfoBuilder.h" class KisOpenGLImageTextures; typedef KisSharedPtr KisOpenGLImageTexturesSP; class KoColorProfile; class KisTextureTileUpdateInfoPoolCollection; typedef QSharedPointer KisTextureTileInfoPoolSP; class KisProofingConfiguration; typedef QSharedPointer KisProofingConfigurationSP; + /** * A set of OpenGL textures that contains the projection of a KisImage. */ -class KRITAUI_EXPORT KisOpenGLImageTextures : public KisShared +class KRITAUI_EXPORT KisOpenGLImageTextures : public QObject, public KisShared { + Q_OBJECT public: /** * Obtain a KisOpenGLImageTextures object for the given image. * @param image The image * @param monitorProfile The profile of the display device * @param renderingIntent The rendering intent * @param conversionFlags The color conversion flags */ static KisOpenGLImageTexturesSP getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Default constructor. */ KisOpenGLImageTextures(); /** * Destructor. */ virtual ~KisOpenGLImageTextures(); /** * \return the image associated with the textures */ KisImageSP image() const; /** * Set the color profile of the display device. * @param monitorProfile The color profile of the display device * @param renderingIntent The rendering intent * @param conversionFlags The color conversion flags */ void setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Update the textures when the color space of the image changes. * @return true when a full data refetch should be initiated by the caller */ bool setImageColorSpace(const KoColorSpace *cs); /** * Complete initialization can only happen once an OpenGL context has been created. * @param f Pointer to OpenGL functions. They must already be initialized. */ void initGL(QOpenGLFunctions *f); void setChannelFlags(const QBitArray &channelFlags); void setProofingConfig(KisProofingConfigurationSP); bool internalColorManagementActive() const; bool setInternalColorManagementActive(bool value); /** * The background checkers texture. */ static const int BACKGROUND_TEXTURE_CHECK_SIZE = 32; static const int BACKGROUND_TEXTURE_SIZE = BACKGROUND_TEXTURE_CHECK_SIZE * 2; /** * Generate a background texture from the given QImage. This is used for the checker * pattern on which the image is rendered. */ void generateCheckerTexture(const QImage & checkImage); GLuint checkerTexture(); void updateConfig(bool useBuffer, int NumMipmapLevels); public: inline QRect storedImageBounds() { return m_storedImageBounds; } inline int xToCol(int x) { return x / m_texturesInfo.effectiveWidth; } inline int yToRow(int y) { return y / m_texturesInfo.effectiveHeight; } inline KisTextureTile* getTextureTileCR(int col, int row) { if (m_initialized) { int tile = row * m_numCols + col; KIS_ASSERT_RECOVER_RETURN_VALUE(m_textureTiles.size() > tile, 0); return m_textureTiles[tile]; } return 0; } inline qreal texelSize() const { Q_ASSERT(m_texturesInfo.width == m_texturesInfo.height); return 1.0 / m_texturesInfo.width; } KisOpenGLUpdateInfoSP updateCache(const QRect& rect, KisImageSP srcImage); KisOpenGLUpdateInfoSP updateCacheNoConversion(const QRect& rect); void recalculateCache(KisUpdateInfoSP info, bool blockMipmapRegeneration); void slotImageSizeChanged(qint32 w, qint32 h); KisOpenGLUpdateInfoBuilder& updateInfoBuilder(); +Q_SIGNALS: + void sigShowFloatingMessage(const QString &message, int timeout, bool priority); + protected: KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); void recreateImageTextureTiles(); void destroyImageTextureTiles(); static bool imageCanShareTextures(); private: void getTextureSize(KisGLTexturesInfo *texturesInfo); void updateTextureFormat(); KisOpenGLUpdateInfoSP updateCacheImpl(const QRect& rect, KisImageSP srcImage, bool convertColorSpace); private: KisImageWSP m_image; QRect m_storedImageBounds; const KoColorProfile *m_monitorProfile; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; /** * Shows whether the internal color management should be enabled or not. * Please note that if you disable color management, *but* your image color * space will not be supported (non-RGB), then it will be enabled anyway. * And this valiable will hold the real state of affairs! */ bool m_internalColorManagementActive; GLuint m_checkerTexture; KisGLTexturesInfo m_texturesInfo; int m_numCols; QVector m_textureTiles; QOpenGLFunctions *m_glFuncs; bool m_useOcio; bool m_initialized; KisOpenGLUpdateInfoBuilder m_updateInfoBuilder; private: typedef QMap ImageTexturesMap; static ImageTexturesMap imageTexturesMap; }; #endif // KIS_OPENGL_IMAGE_TEXTURES_H_ diff --git a/libs/ui/tool/kis_smoothing_options.cpp b/libs/ui/tool/kis_smoothing_options.cpp index f91a3b05b3..592f1d1e1e 100644 --- a/libs/ui/tool/kis_smoothing_options.cpp +++ b/libs/ui/tool/kis_smoothing_options.cpp @@ -1,171 +1,175 @@ /* * Copyright (c) 2012 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_smoothing_options.h" #include "kis_config.h" #include "kis_signal_compressor.h" struct KisSmoothingOptions::Private { - Private() : writeCompressor(500, KisSignalCompressor::FIRST_ACTIVE) {} + Private(bool useSavedSmoothing) + : writeCompressor(500, KisSignalCompressor::FIRST_ACTIVE) + { + KisConfig cfg(true); + smoothingType = (SmoothingType)cfg.lineSmoothingType(!useSavedSmoothing); + smoothnessDistance = cfg.lineSmoothingDistance(!useSavedSmoothing); + tailAggressiveness = cfg.lineSmoothingTailAggressiveness(!useSavedSmoothing); + smoothPressure = cfg.lineSmoothingSmoothPressure(!useSavedSmoothing); + useScalableDistance = cfg.lineSmoothingScalableDistance(!useSavedSmoothing); + delayDistance = cfg.lineSmoothingDelayDistance(!useSavedSmoothing); + useDelayDistance = cfg.lineSmoothingUseDelayDistance(!useSavedSmoothing); + finishStabilizedCurve = cfg.lineSmoothingFinishStabilizedCurve(!useSavedSmoothing); + stabilizeSensors = cfg.lineSmoothingStabilizeSensors(!useSavedSmoothing); + } + KisSignalCompressor writeCompressor; SmoothingType smoothingType; qreal smoothnessDistance; qreal tailAggressiveness; bool smoothPressure; bool useScalableDistance; qreal delayDistance; bool useDelayDistance; bool finishStabilizedCurve; bool stabilizeSensors; }; KisSmoothingOptions::KisSmoothingOptions(bool useSavedSmoothing) - : m_d(new Private) -{ - KisConfig cfg(true); - m_d->smoothingType = (SmoothingType)cfg.lineSmoothingType(!useSavedSmoothing); - m_d->smoothnessDistance = cfg.lineSmoothingDistance(!useSavedSmoothing); - m_d->tailAggressiveness = cfg.lineSmoothingTailAggressiveness(!useSavedSmoothing); - m_d->smoothPressure = cfg.lineSmoothingSmoothPressure(!useSavedSmoothing); - m_d->useScalableDistance = cfg.lineSmoothingScalableDistance(!useSavedSmoothing); - m_d->delayDistance = cfg.lineSmoothingDelayDistance(!useSavedSmoothing); - m_d->useDelayDistance = cfg.lineSmoothingUseDelayDistance(!useSavedSmoothing); - m_d->finishStabilizedCurve = cfg.lineSmoothingFinishStabilizedCurve(!useSavedSmoothing); - m_d->stabilizeSensors = cfg.lineSmoothingStabilizeSensors(!useSavedSmoothing); + : m_d(new Private(useSavedSmoothing)) +{ connect(&m_d->writeCompressor, SIGNAL(timeout()), this, SLOT(slotWriteConfig())); } KisSmoothingOptions::~KisSmoothingOptions() { } KisSmoothingOptions::SmoothingType KisSmoothingOptions::smoothingType() const { return m_d->smoothingType; } void KisSmoothingOptions::setSmoothingType(KisSmoothingOptions::SmoothingType value) { m_d->smoothingType = value; emit sigSmoothingTypeChanged(); m_d->writeCompressor.start(); } qreal KisSmoothingOptions::smoothnessDistance() const { return m_d->smoothnessDistance; } void KisSmoothingOptions::setSmoothnessDistance(qreal value) { m_d->smoothnessDistance = value; m_d->writeCompressor.start(); } qreal KisSmoothingOptions::tailAggressiveness() const { return m_d->tailAggressiveness; } void KisSmoothingOptions::setTailAggressiveness(qreal value) { m_d->tailAggressiveness = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::smoothPressure() const { return m_d->smoothPressure; } void KisSmoothingOptions::setSmoothPressure(bool value) { m_d->smoothPressure = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::useScalableDistance() const { return m_d->useScalableDistance; } void KisSmoothingOptions::setUseScalableDistance(bool value) { m_d->useScalableDistance = value; m_d->writeCompressor.start(); } qreal KisSmoothingOptions::delayDistance() const { return m_d->delayDistance; } void KisSmoothingOptions::setDelayDistance(qreal value) { m_d->delayDistance = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::useDelayDistance() const { return m_d->useDelayDistance; } void KisSmoothingOptions::setUseDelayDistance(bool value) { m_d->useDelayDistance = value; m_d->writeCompressor.start(); } void KisSmoothingOptions::setFinishStabilizedCurve(bool value) { m_d->finishStabilizedCurve = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::finishStabilizedCurve() const { return m_d->finishStabilizedCurve; } void KisSmoothingOptions::setStabilizeSensors(bool value) { m_d->stabilizeSensors = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::stabilizeSensors() const { return m_d->stabilizeSensors; } void KisSmoothingOptions::slotWriteConfig() { KisConfig cfg(false); cfg.setLineSmoothingType(m_d->smoothingType); cfg.setLineSmoothingDistance(m_d->smoothnessDistance); cfg.setLineSmoothingTailAggressiveness(m_d->tailAggressiveness); cfg.setLineSmoothingSmoothPressure(m_d->smoothPressure); cfg.setLineSmoothingScalableDistance(m_d->useScalableDistance); cfg.setLineSmoothingDelayDistance(m_d->delayDistance); cfg.setLineSmoothingUseDelayDistance(m_d->useDelayDistance); cfg.setLineSmoothingFinishStabilizedCurve(m_d->finishStabilizedCurve); cfg.setLineSmoothingStabilizeSensors(m_d->stabilizeSensors); } diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index e834a18d79..93587fe682 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,668 +1,675 @@ /* * Copyright (c) 2006, 2010 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_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged(bool)), SLOT(resetCursorStyle())); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceProvider::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } d->m_isActive = true; emit isActiveChanged(true); } void KisTool::deactivate() { d->m_isActive = false; emit isActiveChanged(false); KoToolBase::deactivate(); } void KisTool::canvasResourceChanged(int key, const QVariant & v) { QString formattedBrushName; if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { formattedBrushName = v.value()->name().replace("_", " "); } switch (key) { case(KoCanvasResourceProvider::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceProvider::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = v.value(); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): emit statusTextChanged(formattedBrushName); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToImagePixelCoordFloored(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToImagePixelFloored(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), int(rect.right()) / image()->xRes(), int( rect.bottom()) / image()->yRes()); return r; } qreal KisTool::convertToPt(qreal value) { const qreal avgResolution = 0.5 * (image()->xRes() + image()->yRes()); return value / avgResolution; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) { return pixelRect; } QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return {topLeft, bottomRight}; } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } +void KisTool::notifyModified() const +{ + if (image()) { + image()->setModified(); + } +} + KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } void KisTool::setCursor(const QCursor &cursor) { d->cursor = cursor; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseTripleClickEvent(KoPointerEvent *event) { mouseDoubleClickEvent(event); } void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); if (!blockUntilOperationsFinished()) { return; } if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } KisTool::NodePaintAbility KisTool::nodePaintAbility() { KisNodeSP node = currentNode(); if (!node) { return NodePaintAbility::UNPAINTABLE; } if (node->inherits("KisShapeLayer")) { return NodePaintAbility::VECTOR; } if (node->inherits("KisCloneLayer")) { return NodePaintAbility::CLONE; } if (node->paintDevice()) { return NodePaintAbility::PAINT; } return NodePaintAbility::UNPAINTABLE; } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->save(); painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); painter->restore(); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::blockUntilOperationsFinished() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->blockUntilOperationsFinished(image()); } void KisTool::blockUntilOperationsFinishedForced() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->blockUntilOperationsFinishedForced(image()); } bool KisTool::isActive() const { return d->m_isActive; } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool blockedNoIndirectPainting = false; const bool presetUsesIndirectPainting = !currentPaintOpPreset()->settings()->paintIncremental(); if (!presetUsesIndirectPainting) { const KisIndirectPaintingSupport *indirectPaintingLayer = dynamic_cast(node.data()); if (indirectPaintingLayer) { blockedNoIndirectPainting = !indirectPaintingLayer->supportsNonIndirectPainting(); } } bool nodeEditable = node->isEditable() && !blockedNoIndirectPainting; if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else if (blockedNoIndirectPainting) { message = i18n("Layer can be painted in Wash Mode only."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h index 1aaab8ac3b..fdbd5e67f9 100644 --- a/libs/ui/tool/kis_tool.h +++ b/libs/ui/tool/kis_tool.h @@ -1,326 +1,329 @@ /* * 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. */ #ifndef KIS_TOOL_H_ #define KIS_TOOL_H_ #include #include #include #include #include #include #include #ifdef __GNUC__ #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!" #else #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!" #endif #define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; } class KoCanvasBase; class KoPattern; class KoAbstractGradient; class KisFilterConfiguration; class QPainter; class QPainterPath; class QPolygonF; /// Definitions of the toolgroups of Krita static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer; static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc. static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels //activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always"; class KRITAUI_EXPORT KisTool : public KoToolBase { Q_OBJECT Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) public: enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02, FLAG_USES_CUSTOM_SIZE=0x04 }; KisTool(KoCanvasBase * canvas, const QCursor & cursor); ~KisTool() override; virtual int flags() const { return 0; } void deleteSelection() override; // KoToolBase Implementation. public: /** * Called by KisToolProxy when the primary action of the tool is * going to be started now, that is when all the modifiers are * pressed and the only thing left is just to press the mouse * button. On coming of this callback the tool is supposed to * prepare the cursor and/or the outline to show the user shat is * going to happen next */ virtual void activatePrimaryAction(); /** * Called by KisToolProxy when the primary is no longer possible * to be started now, e.g. when its modifiers and released. The * tool is supposed revert all the preparetions it has doen in * activatePrimaryAction(). */ virtual void deactivatePrimaryAction(); /** * Called by KisToolProxy when a primary action for the tool is * started. The \p event stores the original event that * started the stroke. The \p event is _accepted_ by default. If * the tool decides to ignore this particular action (e.g. when * the node is not editable), it should call event->ignore(). Then * no further continuePrimaryAction() or endPrimaryAction() will * be called until the next user action. */ virtual void beginPrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is in progress * of pointer movement. If the tool has ignored the event in * beginPrimaryAction(), this method will not be called. */ virtual void continuePrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is being * finished, that is while mouseRelease or tabletRelease event. * If the tool has ignored the event in beginPrimaryAction(), this * method will not be called. */ virtual void endPrimaryAction(KoPointerEvent *event); /** * The same as beginPrimaryAction(), but called when the stroke is * started by a double-click * * \see beginPrimaryAction() */ virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event); /** * Returns true if the tool can handle (and wants to handle) a * very tight flow of input events from the tablet */ virtual bool primaryActionSupportsHiResEvents() const; enum ToolAction { Primary, AlternateChangeSize, AlternatePickFgNode, AlternatePickBgNode, AlternatePickFgImage, AlternatePickBgImage, AlternateSecondary, AlternateThird, AlternateFourth, AlternateFifth, Alternate_NONE = 10000 }; // Technically users are allowed to configure this, but nobody ever would do that. // So these can basically be thought of as aliases to ctrl+click, etc. enum AlternateAction { ChangeSize = AlternateChangeSize, // Default: Shift+Left click PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click Secondary = AlternateSecondary, Third = AlternateThird, Fourth = AlternateFourth, Fifth = AlternateFifth, NONE = 10000 }; enum NodePaintAbility { VECTOR, CLONE, PAINT, UNPAINTABLE }; Q_ENUMS(NodePaintAbility) static AlternateAction actionToAlternateAction(ToolAction action); virtual void activateAlternateAction(AlternateAction action); virtual void deactivateAlternateAction(AlternateAction action); virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action); void mousePressEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void mouseTripleClickEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; bool isActive() const; KisTool::NodePaintAbility nodePaintAbility(); public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; void canvasResourceChanged(int key, const QVariant & res) override; // Implement this slot in case there are any widgets or properties which need // to be updated after certain operations, to reflect the inner state correctly. // At the moment this is used for smoothing options in the freehand brush, but // this will likely be expanded. virtual void updateSettingsViews(); Q_SIGNALS: void isActiveChanged(bool isActivated); protected: // conversion methods are also needed by the paint information builder friend class KisToolPaintingInformationBuilder; /// Convert from native (postscript points) to image pixel /// coordinates. QPointF convertToPixelCoord(KoPointerEvent *e); QPointF convertToPixelCoord(const QPointF& pt); QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true); QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF()); protected: QPointF widgetCenterInWidgetPixels(); QPointF convertDocumentToWidget(const QPointF& pt); /// Convert from native (postscript points) to integer image pixel /// coordinates. This rounds down (not truncate) the pixel coordinates and /// should be used in preference to QPointF::toPoint(), which rounds, /// to ensure the cursor acts on the pixel it is visually over. QPoint convertToImagePixelCoordFloored(KoPointerEvent *e); QRectF convertToPt(const QRectF &rect); qreal convertToPt(qreal value); QPointF viewToPixel(const QPointF &viewCoord) const; /// Convert an integer pixel coordinate into a view coordinate. /// The view coordinate is at the centre of the pixel. QPointF pixelToView(const QPoint &pixelCoord) const; /// Convert a floating point pixel coordinate into a view coordinate. QPointF pixelToView(const QPointF &pixelCoord) const; /// Convert a pixel rectangle into a view rectangle. QRectF pixelToView(const QRectF &pixelRect) const; /// Convert a pixel path into a view path QPainterPath pixelToView(const QPainterPath &pixelPath) const; /// Convert a pixel polygon into a view path QPolygonF pixelToView(const QPolygonF &pixelPolygon) const; /// Update the canvas for the given rectangle in image pixel coordinates. void updateCanvasPixelRect(const QRectF &pixelRect); /// Update the canvas for the given rectangle in view coordinates. void updateCanvasViewRect(const QRectF &viewRect); QWidget* createOptionWidget() override; /** * To determine whether this tool will change its behavior when * modifier keys are pressed */ virtual bool listeningToModifiers(); /** * Request that this tool no longer listen to modifier keys * (Responding to the request is optional) */ virtual void listenToModifiers(bool listen); protected: KisImageWSP image() const; QCursor cursor() const; + /// Call this to set the document modified + void notifyModified() const; + KisImageWSP currentImage(); KoPattern* currentPattern(); KoAbstractGradient *currentGradient(); KisNodeSP currentNode() const; KisNodeList selectedNodes() const; KoColor currentFgColor(); KoColor currentBgColor(); KisPaintOpPresetSP currentPaintOpPreset(); KisFilterConfigurationSP currentGenerator(); /// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible /// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates. void paintToolOutline(QPainter * painter, const QPainterPath &path); /// Checks checks if the current node is editable bool nodeEditable(); /// Checks checks if the selection is editable, only applies to local selection as global selection is always editable bool selectionEditable(); /// Override the cursor appropriately if current node is not editable bool overrideCursorIfNotEditable(); bool blockUntilOperationsFinished(); void blockUntilOperationsFinishedForced(); protected: enum ToolMode: int { HOVER_MODE, PAINT_MODE, SECONDARY_PAINT_MODE, MIRROR_AXIS_SETUP_MODE, GESTURE_MODE, PAN_MODE, OTHER, // tool-specific modes, like multibrush's symmetry axis setup OTHER_1 }; virtual void setMode(ToolMode mode); virtual ToolMode mode() const; void setCursor(const QCursor &cursor); protected Q_SLOTS: /** * Called whenever the configuration settings change. */ virtual void resetCursorStyle(); private: struct Private; Private* const d; }; #endif // KIS_TOOL_H_ diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc index 43059f8ab1..4449fe6065 100644 --- a/libs/ui/tool/kis_tool_freehand.cc +++ b/libs/ui/tool/kis_tool_freehand.cc @@ -1,457 +1,458 @@ /* * kis_tool_freehand.cc - part of Krita * * Copyright (c) 2003-2007 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * Copyright (c) 2007,2008,2010 Cyrille Berger * Copyright (c) 2009 Lukáš Tvrdý * * 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_freehand.h" #include #include #include #include #include #include #include #include #include #include //pop up palette #include // Krita/image #include #include #include #include #include #include // Krita/ui #include "kis_abstract_perspective_grid.h" #include "kis_config.h" #include "canvas/kis_canvas2.h" #include "kis_cursor.h" #include #include #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "strokes/freehand_stroke.h" using namespace std::placeholders; // For _1 placeholder KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText) : KisToolPaint(canvas, cursor), m_paintopBasedPickingInAction(false), m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1)) { m_assistant = false; m_magnetism = 1.0; m_only_one_assistant = true; setSupportOutline(true); setMaskSyntheticEvents(KisConfig(true).disableTouchOnCanvas()); // Disallow mouse events from finger presses unless enabled m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this); m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText); connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline())); } KisToolFreehand::~KisToolFreehand() { delete m_helper; delete m_infoBuilder; } void KisToolFreehand::mouseMoveEvent(KoPointerEvent *event) { KisToolPaint::mouseMoveEvent(event); m_helper->cursorMoved(convertToPixelCoord(event)); } KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const { return m_helper->smoothingOptions(); } void KisToolFreehand::resetCursorStyle() { KisConfig cfg(true); switch (cfg.newCursorStyle()) { case CURSOR_STYLE_NO_CURSOR: useCursor(KisCursor::blankCursor()); break; case CURSOR_STYLE_POINTER: useCursor(KisCursor::arrowCursor()); break; case CURSOR_STYLE_SMALL_ROUND: useCursor(KisCursor::roundCursor()); break; case CURSOR_STYLE_CROSSHAIR: useCursor(KisCursor::crossCursor()); break; case CURSOR_STYLE_TRIANGLE_RIGHTHANDED: useCursor(KisCursor::triangleRightHandedCursor()); break; case CURSOR_STYLE_TRIANGLE_LEFTHANDED: useCursor(KisCursor::triangleLeftHandedCursor()); break; case CURSOR_STYLE_BLACK_PIXEL: useCursor(KisCursor::pixelBlackCursor()); break; case CURSOR_STYLE_WHITE_PIXEL: useCursor(KisCursor::pixelWhiteCursor()); break; case CURSOR_STYLE_TOOLICON: default: KisToolPaint::resetCursorStyle(); break; } } KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const { return m_infoBuilder; } void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper) { delete m_helper; m_helper = helper; } int KisToolFreehand::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } void KisToolFreehand::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolFreehand::deactivate() { if (mode() == PAINT_MODE) { endStroke(); setMode(KisTool::HOVER_MODE); } KisToolPaint::deactivate(); } void KisToolFreehand::initStroke(KoPointerEvent *event) { m_helper->initPaint(event, convertToPixelCoord(event), canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolFreehand::doStroke(KoPointerEvent *event) { m_helper->paintEvent(event); } void KisToolFreehand::endStroke() { m_helper->endPaint(); bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mouseReleaseEvent(); Q_UNUSED(paintOpIgnoredEvent); } bool KisToolFreehand::primaryActionSupportsHiResEvents() const { return true; } void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event) { // FIXME: workaround for the Duplicate Op tryPickByPaintOp(event, PickFgImage); requestUpdateOutline(event->point, event); NodePaintAbility paintability = nodePaintAbility(); // XXX: move this to KisTool and make it work properly for clone layers: for clone layers, the shape paint tools don't work either if (!nodeEditable() || paintability != PAINT) { if (paintability == KisToolPaint::VECTOR || paintability == KisToolPaint::CLONE){ KisCanvas2 * kiscanvas = static_cast(canvas()); QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask."); kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); } event->ignore(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(!m_helper->isRunning()); setMode(KisTool::PAINT_MODE); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->disableControls(); } initStroke(event); } void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); requestUpdateOutline(event->point, event); /** * Actual painting */ doStroke(event); } void KisToolFreehand::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); endStroke(); if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->endStroke(); } + notifyModified(); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->enableControls(); } setMode(KisTool::HOVER_MODE); } bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action) { if (action != PickFgNode && action != PickFgImage) return false; /** * FIXME: we need some better way to implement modifiers * for a paintop level. This method is used in DuplicateOp only! */ QPointF pos = adjustPosition(event->point, event->point); qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) { if (grid && grid->contains(pos)) { perspective = grid->distance(pos); break; } } if (!currentPaintOpPreset()) { return false; } bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()-> mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point), m_infoBuilder->pressureToCurve(event->pressure()), event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, 0, 0), event->modifiers(), currentNode()); // DuplicateOP during the picking of new source point (origin) // is the only paintop that returns "false" here return !paintOpIgnoredEvent; } void KisToolFreehand::activateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::activateAlternateAction(action); return; } useCursor(KisCursor::blankCursor()); setOutlineEnabled(true); } void KisToolFreehand::deactivateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::deactivateAlternateAction(action); return; } resetCursorStyle(); setOutlineEnabled(false); } void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action)) { m_paintopBasedPickingInAction = true; return; } if (action != ChangeSize) { KisToolPaint::beginAlternateAction(event, action); return; } setMode(GESTURE_MODE); m_initialGestureDocPoint = event->point; m_initialGestureGlobalPoint = QCursor::pos(); m_lastDocumentPoint = event->point; m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize(); } void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return; if (action != ChangeSize) { KisToolPaint::continueAlternateAction(event, action); return; } QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint); QPointF actualWidgetPosition = convertDocumentToWidget(event->point); QPointF offset = actualWidgetPosition - lastWidgetPosition; KisCanvas2 *canvas2 = dynamic_cast(canvas()); QRect screenRect = QApplication::desktop()->screenGeometry(); qreal scaleX = 0; qreal scaleY = 0; canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY); const qreal maxBrushSize = KisConfig(true).readEntry("maximumBrushSize", 1000); const qreal effectiveMaxDragSize = 0.5 * screenRect.width(); const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX); const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize; const qreal sizeDiff = scaleCoeff * offset.x() ; if (qAbs(sizeDiff) > 0.01) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); //m_brushResizeCompressor.start(newSize); m_lastDocumentPoint = event->point; m_lastPaintOpSize = newSize; } } void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) { m_paintopBasedPickingInAction = false; return; } if (action != ChangeSize) { KisToolPaint::endAlternateAction(event, action); return; } QCursor::setPos(m_initialGestureGlobalPoint); requestUpdateOutline(m_initialGestureDocPoint, 0); setMode(HOVER_MODE); } bool KisToolFreehand::wantsAutoScroll() const { return false; } void KisToolFreehand::setAssistant(bool assistant) { m_assistant = assistant; } void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant) { m_only_one_assistant = assistant; } void KisToolFreehand::slotDoResizeBrush(qreal newSize) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); } QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant); QPointF ap = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin); return (1.0 - m_magnetism) * point + m_magnetism * ap; } return point; } qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint) { qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) { if (grid && grid->contains(documentPoint)) { perspective = grid->distance(documentPoint); break; } } return perspective; } void KisToolFreehand::explicitUpdateOutline() { requestUpdateOutline(m_outlineDocPoint, 0); } QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { QPointF imagePos = convertToPixelCoord(documentPos); if (currentPaintOpPreset()) return m_helper->paintOpOutline(imagePos, event, currentPaintOpPreset()->settings(), outlineMode); else return QPainterPath(); } diff --git a/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp b/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp index e3f95cabd6..89c82949be 100644 --- a/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp +++ b/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp @@ -1,62 +1,58 @@ /* * 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 "KisFreehandStrokeInfo.h" #include #include KisFreehandStrokeInfo::KisFreehandStrokeInfo() - : painter(new KisPainter()), - dragDistance(new KisDistanceInformation()), - m_parentStrokeInfo(0), - m_childStrokeInfo(0) + : painter(new KisPainter()) + , dragDistance(new KisDistanceInformation()) { } KisFreehandStrokeInfo::KisFreehandStrokeInfo(const KisDistanceInformation &startDist) - : painter(new KisPainter()), - dragDistance(new KisDistanceInformation(startDist)), - m_parentStrokeInfo(0), - m_childStrokeInfo(0) + : painter(new KisPainter()) + , dragDistance(new KisDistanceInformation(startDist)) { } KisFreehandStrokeInfo::KisFreehandStrokeInfo(KisFreehandStrokeInfo *rhs, int levelOfDetail) - : painter(new KisPainter()), - dragDistance(new KisDistanceInformation(*rhs->dragDistance, levelOfDetail)), - m_parentStrokeInfo(rhs) + : painter(new KisPainter()) + , dragDistance(new KisDistanceInformation(*rhs->dragDistance, levelOfDetail)) + , m_parentStrokeInfo(rhs) { rhs->m_childStrokeInfo = this; } KisFreehandStrokeInfo::~KisFreehandStrokeInfo() { if (m_parentStrokeInfo) { m_parentStrokeInfo->m_childStrokeInfo = 0; } delete(painter); delete(dragDistance); } KisDistanceInformation* KisFreehandStrokeInfo::buddyDragDistance() { return m_childStrokeInfo ? m_childStrokeInfo->dragDistance : 0; } diff --git a/libs/ui/tool/strokes/KisFreehandStrokeInfo.h b/libs/ui/tool/strokes/KisFreehandStrokeInfo.h index 88dee42217..c430e0c5e6 100644 --- a/libs/ui/tool/strokes/KisFreehandStrokeInfo.h +++ b/libs/ui/tool/strokes/KisFreehandStrokeInfo.h @@ -1,56 +1,56 @@ /* * 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. */ #ifndef KISPAINTINGSTROKEDATA_H #define KISPAINTINGSTROKEDATA_H #include "kritaui_export.h" class KisPainter; class KisDistanceInformation; /** * The distance information should be associated with each * painting stroke individually, so we store and manipulate * with them together using KisPaintingStrokeInfo structure */ class KRITAUI_EXPORT KisFreehandStrokeInfo { public: KisFreehandStrokeInfo(); KisFreehandStrokeInfo(const KisDistanceInformation &startDist); KisFreehandStrokeInfo(KisFreehandStrokeInfo *rhs, int levelOfDetail); ~KisFreehandStrokeInfo(); KisPainter *painter; KisDistanceInformation *dragDistance; /** * The distance inforametion of the associated LodN * stroke. Returns zero if LodN stroke has already finished * execution or does not exist. */ KisDistanceInformation* buddyDragDistance(); private: - KisFreehandStrokeInfo *m_parentStrokeInfo; - KisFreehandStrokeInfo *m_childStrokeInfo; + KisFreehandStrokeInfo *m_parentStrokeInfo {0}; + KisFreehandStrokeInfo *m_childStrokeInfo {0}; }; #endif // KISPAINTINGSTROKEDATA_H diff --git a/libs/ui/widgets/kis_cie_tongue_widget.cpp b/libs/ui/widgets/kis_cie_tongue_widget.cpp index a88c8c363b..79a8bfc684 100644 --- a/libs/ui/widgets/kis_cie_tongue_widget.cpp +++ b/libs/ui/widgets/kis_cie_tongue_widget.cpp @@ -1,734 +1,732 @@ /* * Copyright (C) 2015 by Wolthera van Hövell tot Westerflier * * Based on the Digikam CIE Tongue widget * Copyright (C) 2006-2013 by Gilles Caulier * * Any source code are inspired from lprof project and * Copyright (C) 1998-2001 Marti Maria * * 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, 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. **/ /** The following table gives the CIE color matching functions \f$\bar{x}(\lambda)\f$, \f$\bar{y}(\lambda)\f$, and \f$\bar{z}(\lambda)\f$, for wavelengths \f$\lambda\f$ at 5 nanometer increments from 380 nm through 780 nm. This table is used in conjunction with Planck's law for the energy spectrum of a black body at a given temperature to plot the black body curve on the CIE chart. The following table gives the spectral chromaticity co-ordinates \f$x(\lambda)\f$ and \f$y(\lambda)\f$ for wavelengths in 5 nanometer increments from 380 nm through 780 nm. These coordinates represent the position in the CIE x-y space of pure spectral colors of the given wavelength, and thus define the outline of the CIE "tongue" diagram. */ #include #include #include #include #include #include #include #include #include #include #include #include "kis_cie_tongue_widget.h" static const double spectral_chromaticity[81][3] = { { 0.1741, 0.0050 }, // 380 nm { 0.1740, 0.0050 }, { 0.1738, 0.0049 }, { 0.1736, 0.0049 }, { 0.1733, 0.0048 }, { 0.1730, 0.0048 }, { 0.1726, 0.0048 }, { 0.1721, 0.0048 }, { 0.1714, 0.0051 }, { 0.1703, 0.0058 }, { 0.1689, 0.0069 }, { 0.1669, 0.0086 }, { 0.1644, 0.0109 }, { 0.1611, 0.0138 }, { 0.1566, 0.0177 }, { 0.1510, 0.0227 }, { 0.1440, 0.0297 }, { 0.1355, 0.0399 }, { 0.1241, 0.0578 }, { 0.1096, 0.0868 }, { 0.0913, 0.1327 }, { 0.0687, 0.2007 }, { 0.0454, 0.2950 }, { 0.0235, 0.4127 }, { 0.0082, 0.5384 }, { 0.0039, 0.6548 }, { 0.0139, 0.7502 }, { 0.0389, 0.8120 }, { 0.0743, 0.8338 }, { 0.1142, 0.8262 }, { 0.1547, 0.8059 }, { 0.1929, 0.7816 }, { 0.2296, 0.7543 }, { 0.2658, 0.7243 }, { 0.3016, 0.6923 }, { 0.3373, 0.6589 }, { 0.3731, 0.6245 }, { 0.4087, 0.5896 }, { 0.4441, 0.5547 }, { 0.4788, 0.5202 }, { 0.5125, 0.4866 }, { 0.5448, 0.4544 }, { 0.5752, 0.4242 }, { 0.6029, 0.3965 }, { 0.6270, 0.3725 }, { 0.6482, 0.3514 }, { 0.6658, 0.3340 }, { 0.6801, 0.3197 }, { 0.6915, 0.3083 }, { 0.7006, 0.2993 }, { 0.7079, 0.2920 }, { 0.7140, 0.2859 }, { 0.7190, 0.2809 }, { 0.7230, 0.2770 }, { 0.7260, 0.2740 }, { 0.7283, 0.2717 }, { 0.7300, 0.2700 }, { 0.7311, 0.2689 }, { 0.7320, 0.2680 }, { 0.7327, 0.2673 }, { 0.7334, 0.2666 }, { 0.7340, 0.2660 }, { 0.7344, 0.2656 }, { 0.7346, 0.2654 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 } // 780 nm }; class Q_DECL_HIDDEN KisCIETongueWidget::Private { public: Private() : profileDataAvailable(false), needUpdatePixmap(false), cieTongueNeedsUpdate(true), uncalibratedColor(false), xBias(0), yBias(0), pxcols(0), pxrows(0), progressCount(0), gridside(0), progressTimer(0), Primaries(9), whitePoint(3) { progressPix = KPixmapSequence("process-working", KisIconUtils::SizeSmallMedium); } bool profileDataAvailable; bool needUpdatePixmap; bool cieTongueNeedsUpdate; bool uncalibratedColor; int xBias; int yBias; int pxcols; int pxrows; int progressCount; // Position of animation during loading/calculation. double gridside; QPainter painter; QTimer* progressTimer; QPixmap pixmap; QPixmap cietongue; QPixmap gamutMap; KPixmapSequence progressPix; QVector Primaries; QVector whitePoint; QPolygonF gamut; model colorModel; }; KisCIETongueWidget::KisCIETongueWidget(QWidget *parent) : QWidget(parent), d(new Private) { d->progressTimer = new QTimer(this); setAttribute(Qt::WA_DeleteOnClose); d->Primaries.resize(9); d->Primaries.fill(0.0); d->whitePoint.resize(3); d->whitePoint<<0.34773<<0.35952<<1.0; d->gamut = QPolygonF(); connect(d->progressTimer, SIGNAL(timeout()), this, SLOT(slotProgressTimerDone())); } KisCIETongueWidget::~KisCIETongueWidget() { delete d; } int KisCIETongueWidget::grids(double val) const { return (int) floor(val * d->gridside + 0.5); } void KisCIETongueWidget::setProfileData(QVector p, QVector w, bool profileData) { d->profileDataAvailable = profileData; if (profileData){ d->Primaries= p; d->whitePoint = w; d->needUpdatePixmap = true; } else { return; } } void KisCIETongueWidget::setGamut(QPolygonF gamut) { d->gamut=gamut; } void KisCIETongueWidget::setRGBData(QVector whitepoint, QVector colorants) { if (colorants.size()==9){ d->Primaries= colorants; d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::RGBA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setCMYKData(QVector whitepoint) { if (whitepoint.size()==3){ //d->Primaries= colorants; d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::CMYKA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setXYZData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::XYZA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setGrayData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::GRAYA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setLABData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::LABA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setYCbCrData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::YCbCrA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setProfileDataAvailable(bool dataAvailable) { d->profileDataAvailable = dataAvailable; } void KisCIETongueWidget::mapPoint(int& icx, int& icy, QPointF xy) { icx = (int) floor((xy.x() * (d->pxcols - 1)) + .5); icy = (int) floor(((d->pxrows - 1) - xy.y() * (d->pxrows - 1)) + .5); } void KisCIETongueWidget::biasedLine(int x1, int y1, int x2, int y2) { d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2); } void KisCIETongueWidget::biasedText(int x, int y, const QString& txt) { d->painter.drawText(QPoint(d->xBias + x, y), txt); } QRgb KisCIETongueWidget::colorByCoord(double x, double y) { // Get xyz components scaled from coordinates double cx = ((double) x) / (d->pxcols - 1); double cy = 1.0 - ((double) y) / (d->pxrows - 1); double cz = 1.0 - cx - cy; // Project xyz to XYZ space. Note that in this // particular case we are substituting XYZ with xyz //Need to use KoColor here. const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "U8"); quint8 data[4]; data[0]= cx*255; data[1]= cy*255; data[2]= cz*255; data[3]= 1.0*255; KoColor colXYZ(data, xyzColorSpace); QColor colRGB = colXYZ.toQColor(); return qRgb(colRGB.red(), colRGB.green(), colRGB.blue()); } void KisCIETongueWidget::outlineTongue() { int lx=0, ly=0; int fx=0, fy=0; - for (int x = 380; x <= 700; x += 5) - { + for (int x = 380; x <= 700; x += 5) { int ix = (x - 380) / 5; - QPointF * p = new QPointF(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]); + QPointF p(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]); int icx, icy; - mapPoint(icx, icy, * p); + mapPoint(icx, icy, p); - if (x > 380) - { + if (x > 380) { biasedLine(lx, ly, icx, icy); } - else - { + else { fx = icx; fy = icy; } lx = icx; ly = icy; + } biasedLine(lx, ly, fx, fy); } void KisCIETongueWidget::fillTongue() { QImage Img = d->cietongue.toImage(); int x; for (int y = 0; y < d->pxrows; ++y) { int xe = 0; // Find horizontal extents of tongue on this line. for (x = 0; x < d->pxcols; ++x) { if (QColor(Img.pixel(x + d->xBias, y)) != QColor(Qt::black)) { for (xe = d->pxcols - 1; xe >= x; --xe) { if (QColor(Img.pixel(xe + d->xBias, y)) != QColor(Qt::black)) { break; } } break; } } if (x < d->pxcols) { for ( ; x <= xe; ++x) { QRgb Color = colorByCoord(x, y); Img.setPixel(x + d->xBias, y, Color); } } } d->cietongue = QPixmap::fromImage(Img, Qt::AvoidDither); } void KisCIETongueWidget::drawTongueAxis() { QFont font; font.setPointSize(6); d->painter.setFont(font); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(0, 0, 0, d->pxrows - 1); biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1); for (int y = 1; y <= 9; y += 1) { QString s; int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; s.sprintf("0.%d", y); biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4)); biasedText(xstart - grids(11), d->pxrows + grids(15), s); s.sprintf("0.%d", 10 - y); biasedLine(0, ystart, grids(3), ystart); biasedText(grids(-25), ystart + grids(5), s); } } void KisCIETongueWidget::drawTongueGrid() { d->painter.setPen(qRgb(128, 128, 128)); d->painter.setOpacity(0.5); for (int y = 1; y <= 9; y += 1) { int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1); biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart); } d->painter.setOpacity(1.0); } void KisCIETongueWidget::drawLabels() { QFont font; font.setPointSize(5); d->painter.setFont(font); for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10) { QString wl; int bx = 0, by = 0, tx, ty; if (x < 520) { bx = grids(-22); by = grids(2); } else if (x < 535) { bx = grids(-8); by = grids(-6); } else { bx = grids(4); } int ix = (x - 380) / 5; QPointF p(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]); int icx, icy; mapPoint(icx, icy, p); tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0)); ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2))); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(icx, icy, tx, ty); QRgb Color = colorByCoord(icx, icy); d->painter.setPen(Color); wl.sprintf("%d", x); biasedText(icx+bx, icy+by, wl); } } void KisCIETongueWidget::drawSmallEllipse(QPointF xy, int r, int g, int b, int sz) { int icx, icy; mapPoint(icx, icy, xy); d->painter.save(); d->painter.setRenderHint(QPainter::Antialiasing); d->painter.setPen(qRgb(r, g, b)); d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz); d->painter.setPen(qRgb(r/2, g/2, b/2)); int sz2 = sz-2; d->painter.drawEllipse(icx + d->xBias- sz2/2, icy-sz2/2, sz2, sz2); d->painter.restore(); } void KisCIETongueWidget::drawColorantTriangle() { d->painter.save(); d->painter.setPen(qRgb(80, 80, 80)); d->painter.setRenderHint(QPainter::Antialiasing); if (d->colorModel ==KisCIETongueWidget::RGBA) { drawSmallEllipse((QPointF(d->Primaries[0],d->Primaries[1])), 255, 128, 128, 6); drawSmallEllipse((QPointF(d->Primaries[3],d->Primaries[4])), 128, 255, 128, 6); drawSmallEllipse((QPointF(d->Primaries[6],d->Primaries[7])), 128, 128, 255, 6); int x1, y1, x2, y2, x3, y3; mapPoint(x1, y1, (QPointF(d->Primaries[0],d->Primaries[1])) ); mapPoint(x2, y2, (QPointF(d->Primaries[3],d->Primaries[4])) ); mapPoint(x3, y3, (QPointF(d->Primaries[6],d->Primaries[7])) ); biasedLine(x1, y1, x2, y2); biasedLine(x2, y2, x3, y3); biasedLine(x3, y3, x1, y1); } /*else if (d->colorModel ==CMYK){ for (i=0; iPrimaries.size();i+++){ drawSmallEllipse((QPointF(d->Primaries[0],d->Primaries[1])), 160, 160, 160, 6);//greyscale for now //int x1, y1, x2, y2; //mapPoint(x1, y1, (QPointF(d->Primaries[i],d->Primaries[i+1])) ); //mapPoint(x2, y2, (QPointF(d->Primaries[i+3],d->Primaries[i+4])) ); //biasedLine(x1, y1, x2, y2); } } */ d->painter.restore(); } void KisCIETongueWidget::drawWhitePoint() { drawSmallEllipse(QPointF (d->whitePoint[0],d->whitePoint[1]), 255, 255, 255, 8); } void KisCIETongueWidget::drawGamut() { d->gamutMap=QPixmap(size()); d->gamutMap.fill(Qt::black); QPainter gamutPaint; gamutPaint.begin(&d->gamutMap); QPainterPath path; //gamutPaint.setCompositionMode(QPainter::CompositionMode_Clear); gamutPaint.setRenderHint(QPainter::Antialiasing); path.setFillRule(Qt::WindingFill); gamutPaint.setBrush(Qt::white); gamutPaint.setPen(Qt::white); int x, y = 0; if (!d->gamut.empty()) { gamutPaint.setOpacity(0.5); if (d->colorModel == KisCIETongueWidget::RGBA) { mapPoint(x, y, (QPointF(d->Primaries[0],d->Primaries[1])) ); path.moveTo(QPointF(x + d->xBias,y)); mapPoint(x, y, (QPointF(d->Primaries[3],d->Primaries[4])) ); path.lineTo(QPointF(x + d->xBias,y)); mapPoint(x, y, (QPointF(d->Primaries[6],d->Primaries[7])) ); path.lineTo(QPointF(x + d->xBias,y)); mapPoint(x, y, (QPointF(d->Primaries[0],d->Primaries[1])) ); path.lineTo(QPointF(x + d->xBias,y)); } gamutPaint.drawPath(path); gamutPaint.setOpacity(1.0); foreach (QPointF Point, d->gamut) { mapPoint(x, y, Point); gamutPaint.drawEllipse(x + d->xBias- 2, y-2, 4, 4); //Point.setX(x); //Point.setY(y); //path.lineTo(Point); } } gamutPaint.end(); d->painter.save(); d->painter.setOpacity(0.5); d->painter.setCompositionMode(QPainter::CompositionMode_Multiply); QRect area(d->xBias, 0, d->pxcols, d->pxrows); d->painter.drawPixmap(area,d->gamutMap, area); d->painter.setOpacity(1.0); d->painter.restore(); } void KisCIETongueWidget::updatePixmap() { d->needUpdatePixmap = false; d->pixmap = QPixmap(size()); if (d->cieTongueNeedsUpdate){ // Draw the CIE tongue curve. I don't see why we need to redraw it every time the whitepoint and such changes so we cache it. d->cieTongueNeedsUpdate = false; d->cietongue = QPixmap(size()); d->cietongue.fill(Qt::black); d->painter.begin(&d->cietongue); int pixcols = d->pixmap.width(); int pixrows = d->pixmap.height(); d->gridside = (qMin(pixcols, pixrows)) / 512.0; d->xBias = grids(32); d->yBias = grids(20); d->pxcols = pixcols - d->xBias; d->pxrows = pixrows - d->yBias; d->painter.setBackground(QBrush(qRgb(0, 0, 0))); d->painter.setPen(qRgb(255, 255, 255)); outlineTongue(); d->painter.end(); fillTongue(); d->painter.begin(&d->cietongue); drawTongueAxis(); drawLabels(); drawTongueGrid(); d->painter.end(); } d->pixmap = d->cietongue; d->painter.begin(&d->pixmap); //draw whitepoint and colorants if (d->whitePoint[2] > 0.0) { drawWhitePoint(); } if (d->Primaries[2] != 0.0) { drawColorantTriangle(); } drawGamut(); d->painter.end(); } void KisCIETongueWidget::paintEvent(QPaintEvent*) { QPainter p(this); // Widget is disable : drawing grayed frame. if ( !isEnabled() ) { p.fillRect(0, 0, width(), height(), palette().color(QPalette::Disabled, QPalette::Background)); QPen pen(palette().color(QPalette::Disabled, QPalette::Foreground)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); return; } // No profile data to show, or RAW file if (!d->profileDataAvailable) { p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Background)); QPen pen(palette().color(QPalette::Active, QPalette::Text)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); if (d->uncalibratedColor) { p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Uncalibrated color space")); } else { p.setPen(Qt::red); p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("No profile available...")); } return; } // Create CIE tongue if needed if (d->needUpdatePixmap) { updatePixmap(); } // draw prerendered tongue p.drawPixmap(0, 0, d->pixmap); } void KisCIETongueWidget::resizeEvent(QResizeEvent* event) { Q_UNUSED(event); setMinimumHeight(width()); setMaximumHeight(width()); d->needUpdatePixmap = true; d->cieTongueNeedsUpdate = true; } void KisCIETongueWidget::slotProgressTimerDone() { update(); d->progressTimer->start(200); } diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp index 623d11a572..1c5292d21f 100644 --- a/libs/ui/widgets/kis_curve_widget.cpp +++ b/libs/ui/widgets/kis_curve_widget.cpp @@ -1,535 +1,552 @@ /* * Copyright (c) 2005 C. Boemann * 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. */ // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes. #include #include #include +#include +#include + + // Local includes. #include "widgets/kis_curve_widget.h" #define bounds(x,a,b) (xb ? b :x)) #define MOUSE_AWAY_THRES 15 #define POINT_AREA 1E-4 #define CURVE_AREA 1E-4 #include "kis_curve_widget_p.h" KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new KisCurveWidget::Private(this)) { setObjectName("KisCurveWidget"); d->m_grab_point_index = -1; d->m_readOnlyMode = false; d->m_guideVisible = false; d->m_pixmapDirty = true; d->m_pixmapCache = 0; d->setState(ST_NORMAL); d->m_intIn = 0; d->m_intOut = 0; + connect(&d->m_modifiedSignalsCompressor, SIGNAL(timeout()), SLOT(notifyModified())); + connect(this, SIGNAL(compressorShouldEmitModified()), SLOT(slotCompressorShouldEmitModified())); + setMouseTracking(true); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent); setMinimumSize(150, 50); setMaximumSize(250, 250); - d->setCurveModified(); setFocusPolicy(Qt::StrongFocus); } KisCurveWidget::~KisCurveWidget() { delete d->m_pixmapCache; delete d; } void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax) { + dropInOutControls(); + d->m_intIn = in; d->m_intOut = out; if (!d->m_intIn || !d->m_intOut) return; d->m_inMin = inMin; d->m_inMax = inMax; d->m_outMin = outMin; d->m_outMax = outMax; int realInMin = qMin(inMin, inMax); // tilt elevation has range (90, 0), which QSpinBox can't handle int realInMax = qMax(inMin, inMax); d->m_intIn->setRange(realInMin, realInMax); d->m_intOut->setRange(d->m_outMin, d->m_outMax); connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); d->syncIOControls(); } void KisCurveWidget::dropInOutControls() { if (!d->m_intIn || !d->m_intOut) return; disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); d->m_intIn = d->m_intOut = 0; } void KisCurveWidget::inOutChanged(int) { QPointF pt; Q_ASSERT(d->m_grab_point_index >= 0); pt.setX(d->io2sp(d->m_intIn->value(), d->m_inMin, d->m_inMax)); pt.setY(d->io2sp(d->m_intOut->value(), d->m_outMin, d->m_outMax)); bool newPoint = false; if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) { newPoint = true; d->m_curve.setPoint(d->m_grab_point_index, pt); d->m_grab_point_index = d->m_curve.points().indexOf(pt); emit pointSelectedChanged(); } else { pt = d->m_curve.points()[d->m_grab_point_index]; } if (!newPoint) { // if there is a new Point, no point in rewriting values in spinboxes d->m_intIn->blockSignals(true); d->m_intOut->blockSignals(true); d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax)); d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax)); d->m_intIn->blockSignals(false); d->m_intOut->blockSignals(false); } d->setCurveModified(false); } void KisCurveWidget::reset(void) { d->m_grab_point_index = -1; emit pointSelectedChanged(); d->m_guideVisible = false; //remove total - 2 points. while (d->m_curve.points().count() - 2 ) { d->m_curve.removePoint(d->m_curve.points().count() - 2); } d->setCurveModified(); } void KisCurveWidget::setCurveGuide(const QColor & color) { d->m_guideVisible = true; d->m_colorGuide = color; } void KisCurveWidget::setPixmap(const QPixmap & pix) { d->m_pix = pix; d->m_pixmapDirty = true; d->setCurveRepaint(); } QPixmap KisCurveWidget::getPixmap() { return d->m_pix; } void KisCurveWidget::setBasePixmap(const QPixmap &pix) { d->m_pixmapBase = pix; } QPixmap KisCurveWidget::getBasePixmap() { return d->m_pixmapBase; } bool KisCurveWidget::pointSelected() const { return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1; } void KisCurveWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) { //x() find closest point to get focus afterwards double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x(); int left_of_grab_point_index = d->m_grab_point_index - 1; int right_of_grab_point_index = d->m_grab_point_index + 1; int new_grab_point_index; if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) < fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) { new_grab_point_index = left_of_grab_point_index; } else { new_grab_point_index = d->m_grab_point_index; } d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = new_grab_point_index; emit pointSelectedChanged(); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); } e->accept(); d->setCurveModified(); } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) { d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) ); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); e->accept(); d->setCurveModified(); } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) { /* FIXME: Lets user choose the hotkeys */ addPointInTheMiddle(); e->accept(); } else QWidget::keyPressEvent(e); } void KisCurveWidget::addPointInTheMiddle() { QPointF pt(0.5, d->m_curve.value(0.5)); if (!d->jumpOverExistingPoints(pt, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(pt); emit pointSelectedChanged(); if (d->m_intIn) d->m_intIn->setFocus(Qt::TabFocusReason); d->setCurveModified(); } void KisCurveWidget::resizeEvent(QResizeEvent *e) { d->m_pixmapDirty = true; QWidget::resizeEvent(e); } void KisCurveWidget::paintEvent(QPaintEvent *) { int wWidth = width() - 1; int wHeight = height() - 1; QPainter p(this); // Antialiasing is not a good idea here, because // the grid will drift one pixel to any side due to rounding of int // FIXME: let's user tell the last word (in config) //p.setRenderHint(QPainter::Antialiasing); QPalette appPalette = QApplication::palette(); p.fillRect(rect(), appPalette.color(QPalette::Base)); // clear out previous paint call results // make the entire widget grayed out if it is disabled if (!this->isEnabled()) { p.setOpacity(0.2); } // draw background if (!d->m_pix.isNull()) { if (d->m_pixmapDirty || !d->m_pixmapCache) { delete d->m_pixmapCache; d->m_pixmapCache = new QPixmap(width(), height()); QPainter cachePainter(d->m_pixmapCache); cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height()); cachePainter.drawPixmap(0, 0, d->m_pix); d->m_pixmapDirty = false; } p.drawPixmap(0, 0, *d->m_pixmapCache); } d->drawGrid(p, wWidth, wHeight); KisConfig cfg(true); if (cfg.antialiasCurves()) { p.setRenderHint(QPainter::Antialiasing); } // Draw curve. double curY; double normalizedX; int x; QPolygonF poly; p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); for (x = 0 ; x < wWidth ; x++) { normalizedX = double(x) / wWidth; curY = wHeight - d->m_curve.value(normalizedX) * wHeight; /** * Keep in mind that QLineF rounds doubles * to ints mathematically, not just rounds down * like in C */ poly.append(QPointF(x, curY)); } poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight)); p.drawPolyline(poly); QPainterPath fillCurvePath; QPolygonF fillPoly = poly; fillPoly.append(QPoint(rect().width(), rect().height())); fillPoly.append(QPointF(0,rect().height())); // add a couple points to the edges so it fills in below always QColor fillColor = appPalette.color(QPalette::Text); fillColor.setAlphaF(0.2); fillCurvePath.addPolygon(fillPoly); p.fillPath(fillCurvePath, fillColor); // Drawing curve handles. double curveX; double curveY; if (!d->m_readOnlyMode) { for (int i = 0; i < d->m_curve.points().count(); ++i) { curveX = d->m_curve.points().at(i).x(); curveY = d->m_curve.points().at(i).y(); int handleSize = 12; // how big should control points be (diamater size) if (i == d->m_grab_point_index) { // active point is slightly more "bold" p.setPen(QPen(appPalette.color(QPalette::Text), 4, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), wHeight - (handleSize*0.5) - curveY * wHeight, handleSize, handleSize)); } else { p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), wHeight - (handleSize*0.5) - curveY * wHeight, handleSize, handleSize)); } } } // add border around widget to help contain everything QPainterPath widgetBoundsPath; widgetBoundsPath.addRect(rect()); p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text)); p.setOpacity(1.0); // reset to 1.0 in case we were drawing a disabled widget before } void KisCurveWidget::mousePressEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height()); if (closest_point_index < 0) { QPointF newPoint(x, y); if (!d->jumpOverExistingPoints(newPoint, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(newPoint); emit pointSelectedChanged(); } else { d->m_grab_point_index = closest_point_index; emit pointSelectedChanged(); } d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x(); d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y(); d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x; d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y; d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY)); d->m_draggedAwayPointIndex = -1; d->setState(ST_DRAG); d->setCurveModified(); } void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); d->setCurveModified(); } void KisCurveWidget::mouseMoveEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height()); if (nearestPointIndex < 0) setCursor(Qt::ArrowCursor); else setCursor(Qt::CrossCursor); } else { // Else, drag the selected point bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES || e->pos().x() < -MOUSE_AWAY_THRES; bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES || e->pos().y() < -MOUSE_AWAY_THRES; bool removePoint = (crossedHoriz || crossedVert); if (!removePoint && d->m_draggedAwayPointIndex >= 0) { // point is no longer dragged away so reinsert it QPointF newPoint(d->m_draggedAwayPoint); d->m_grab_point_index = d->m_curve.addPoint(newPoint); d->m_draggedAwayPointIndex = -1; } if (removePoint && (d->m_draggedAwayPointIndex >= 0)) return; setCursor(Qt::CrossCursor); x += d->m_grabOffsetX; y += d->m_grabOffsetY; double leftX; double rightX; if (d->m_grab_point_index == 0) { leftX = 0.0; if (d->m_curve.points().count() > 1) rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; else rightX = 1.0; } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) { leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = 1.0; } else { Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1); // the 1E-4 addition so we can grab the dot later. leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; } x = bounds(x, leftX, rightX); y = bounds(y, 0., 1.); d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y)); if (removePoint && d->m_curve.points().count() > 2) { d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index]; d->m_draggedAwayPointIndex = d->m_grab_point_index; d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1); emit pointSelectedChanged(); } d->setCurveModified(); } } KisCubicCurve KisCurveWidget::curve() { return d->m_curve; } void KisCurveWidget::setCurve(KisCubicCurve inlist) { d->m_curve = inlist; d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1); d->setCurveModified(); emit pointSelectedChanged(); } void KisCurveWidget::leaveEvent(QEvent *) { } +void KisCurveWidget::notifyModified() +{ + emit modified(); +} + +void KisCurveWidget::slotCompressorShouldEmitModified() +{ + d->m_modifiedSignalsCompressor.start(); +} diff --git a/libs/ui/widgets/kis_curve_widget.h b/libs/ui/widgets/kis_curve_widget.h index 719c29093a..5ffca8bdf8 100644 --- a/libs/ui/widgets/kis_curve_widget.h +++ b/libs/ui/widgets/kis_curve_widget.h @@ -1,161 +1,178 @@ /* * Copyright (c) 2005 C. Boemann * 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_CURVE_WIDGET_H #define KIS_CURVE_WIDGET_H // Qt includes. #include #include #include #include #include #include #include #include #include #include class QSpinBox; class KisCubicCurve; /** * KisCurveWidget is a widget that shows a single curve that can be edited * by the user. The user can grab the curve and move it; this creates * a new control point. Control points can be deleted by selecting a point * and pressing the delete key. * * (From: http://techbase.kde.org/Projects/Widgets_and_Classes#KisCurveWidget) * KisCurveWidget allows editing of spline based y=f(x) curves. Handy for cases * where you want the user to control such things as tablet pressure * response, color transformations, acceleration by time, aeroplane lift *by angle of attack. */ class KRITAUI_EXPORT KisCurveWidget : public QWidget { Q_OBJECT Q_PROPERTY(bool pointSelected READ pointSelected NOTIFY pointSelectedChanged); public: friend class CurveEditorItem; /** * Create a new curve widget with a default curve, that is a straight * line from bottom-left to top-right. */ KisCurveWidget(QWidget *parent = 0, Qt::WindowFlags f = 0); ~KisCurveWidget() override; /** * Reset the curve to the default shape */ void reset(void); /** * Enable the guide and set the guide color to the specified color. * * XXX: it seems that the guide feature isn't actually implemented yet? */ void setCurveGuide(const QColor & color); /** * Set a background pixmap. The background pixmap will be drawn under * the grid and the curve. * * XXX: or is the pixmap what is drawn to the left and bottom of the curve * itself? */ void setPixmap(const QPixmap & pix); QPixmap getPixmap(); void setBasePixmap(const QPixmap & pix); QPixmap getBasePixmap(); /** * Whether or not there is a point selected * This does NOT include the first and last points */ bool pointSelected() const; Q_SIGNALS: /** * Emitted whenever a control point has changed position. */ void modified(void); /** * Emitted whenever the status of whether a control point is selected or not changes */ void pointSelectedChanged(); + /** + * Emitted to notify that the start() function in compressor can be activated. + * Thanks to that, blocking signals in curve widget blocks "sending signals" + * (calling start() function) *to* the signal compressor. + * It effectively makes signals work nearly the same way they worked before + * adding the signal compressor in between. + */ + void compressorShouldEmitModified(); + protected Q_SLOTS: void inOutChanged(int); + void notifyModified(); + + /** + * This function is called when compressorShouldEmitModified() is emitted. + * For why it's needed, \see compressorShouldEmitModified() + */ + void slotCompressorShouldEmitModified(); + protected: void keyPressEvent(QKeyEvent *) override; void paintEvent(QPaintEvent *) override; void mousePressEvent(QMouseEvent * e) override; void mouseReleaseEvent(QMouseEvent * e) override; void mouseMoveEvent(QMouseEvent * e) override; void leaveEvent(QEvent *) override; void resizeEvent(QResizeEvent *e) override; public: /** * @return get a list with all defined points. If you want to know what the * y value for a given x is on the curve defined by these points, use getCurveValue(). * @see getCurveValue */ KisCubicCurve curve(); /** * Replace the current curve with a curve specified by the curve defined by the control * points in @p inlist. */ void setCurve(KisCubicCurve inlist); /** * Connect/disconnect external spinboxes to the curve * @p inMin / @p inMax - is the range for input values * @p outMin / @p outMax - is the range for output values */ void setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax); void dropInOutControls(); /** * Handy function that creates new point in the middle * of the curve and sets focus on the @p m_intIn field, * so the user can move this point anywhere in a moment */ void addPointInTheMiddle(); private: class Private; Private * const d; }; #endif /* KIS_CURVE_WIDGET_H */ diff --git a/libs/ui/widgets/kis_curve_widget_p.h b/libs/ui/widgets/kis_curve_widget_p.h index fe40104e8b..0e1c9470d1 100644 --- a/libs/ui/widgets/kis_curve_widget_p.h +++ b/libs/ui/widgets/kis_curve_widget_p.h @@ -1,276 +1,280 @@ /* * Copyright (c) 2005 C. Boemann * 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_CURVE_WIDGET_P_H_ #define _KIS_CURVE_WIDGET_P_H_ #include #include #include #include enum enumState { ST_NORMAL, ST_DRAG }; /** * Private members for KisCurveWidget class */ class Q_DECL_HIDDEN KisCurveWidget::Private { KisCurveWidget *m_curveWidget; KisSpinBoxSplineUnitConverter unitConverter; public: Private(KisCurveWidget *parent); /* Dragging variables */ int m_grab_point_index; double m_grabOffsetX; double m_grabOffsetY; double m_grabOriginalX; double m_grabOriginalY; QPointF m_draggedAwayPoint; int m_draggedAwayPointIndex; bool m_readOnlyMode; bool m_guideVisible; QColor m_colorGuide; /* The curve itself */ bool m_splineDirty; KisCubicCurve m_curve; QPixmap m_pix; QPixmap m_pixmapBase; bool m_pixmapDirty; QPixmap *m_pixmapCache; /* In/Out controls */ QSpinBox *m_intIn; QSpinBox *m_intOut; /* Working range of them */ int m_inMin; int m_inMax; int m_outMin; int m_outMax; /** * State functions. * At the moment used only for dragging. */ enumState m_state; inline void setState(enumState st); inline enumState state() const; + /** + * Compresses the modified() signals + */ + KisThreadSafeSignalCompressor m_modifiedSignalsCompressor; /*** Internal routines ***/ /** * Common update routines */ void setCurveModified(bool rewriteSpinBoxesValues); void setCurveRepaint(); /** * Convert working range of * In/Out controls to normalized * range of spline (and reverse) * See notes on KisSpinBoxSplineUnitConverter */ double io2sp(int x, int min, int max); int sp2io(double x, int min, int max); /** * Check whether newly created/moved point @p pt doesn't overlap * with any of existing ones from @p m_points and adjusts its coordinates. * @p skipIndex is the index of the point, that shouldn't be taken * into account during the search * (e.g. because it's @p pt itself) * * Returns false in case the point can't be placed anywhere * without overlapping */ bool jumpOverExistingPoints(QPointF &pt, int skipIndex); /** * Synchronize In/Out spinboxes with the curve */ void syncIOControls(); /** * Find the nearest point to @p pt from m_points */ int nearestPointInRange(QPointF pt, int wWidth, int wHeight) const; /** * Nothing to be said! =) */ inline void drawGrid(QPainter &p, int wWidth, int wHeight); }; KisCurveWidget::Private::Private(KisCurveWidget *parent) + : m_modifiedSignalsCompressor(100, KisSignalCompressor::Mode::FIRST_INACTIVE) { m_curveWidget = parent; } double KisCurveWidget::Private::io2sp(int x, int min, int max) { return unitConverter.io2sp(x, min, max); } int KisCurveWidget::Private::sp2io(double x, int min, int max) { return unitConverter.sp2io(x, min, max); } bool KisCurveWidget::Private::jumpOverExistingPoints(QPointF &pt, int skipIndex) { Q_FOREACH (const QPointF &it, m_curve.points()) { if (m_curve.points().indexOf(it) == skipIndex) continue; if (fabs(it.x() - pt.x()) < POINT_AREA) { pt.rx() = pt.x() >= it.x() ? it.x() + POINT_AREA : it.x() - POINT_AREA; } } return (pt.x() >= 0 && pt.x() <= 1.); } int KisCurveWidget::Private::nearestPointInRange(QPointF pt, int wWidth, int wHeight) const { double nearestDistanceSquared = 1000; int nearestIndex = -1; int i = 0; Q_FOREACH (const QPointF & point, m_curve.points()) { double distanceSquared = (pt.x() - point.x()) * (pt.x() - point.x()) + (pt.y() - point.y()) * (pt.y() - point.y()); if (distanceSquared < nearestDistanceSquared) { nearestIndex = i; nearestDistanceSquared = distanceSquared; } ++i; } if (nearestIndex >= 0) { if (fabs(pt.x() - m_curve.points()[nearestIndex].x()) *(wWidth - 1) < 5 && fabs(pt.y() - m_curve.points()[nearestIndex].y()) *(wHeight - 1) < 5) { return nearestIndex; } } return -1; } #define div2_round(x) (((x)+1)>>1) #define div4_round(x) (((x)+2)>>2) void KisCurveWidget::Private::drawGrid(QPainter &p, int wWidth, int wHeight) { /** * Hint: widget size should conform * formula 4n+5 to draw grid correctly * without curious shifts between * spline and it caused by rounding * * That is not mandatory but desirable */ QPalette appPalette = QApplication::palette(); p.setPen(QPen(appPalette.color(QPalette::Background), 1, Qt::SolidLine)); p.drawLine(div4_round(wWidth), 0, div4_round(wWidth), wHeight); p.drawLine(div2_round(wWidth), 0, div2_round(wWidth), wHeight); p.drawLine(div4_round(3*wWidth), 0, div4_round(3*wWidth), wHeight); p.drawLine(0, div4_round(wHeight), wWidth, div4_round(wHeight)); p.drawLine(0, div2_round(wHeight), wWidth, div2_round(wHeight)); p.drawLine(0, div4_round(3*wHeight), wWidth, div4_round(3*wHeight)); } void KisCurveWidget::Private::syncIOControls() { if (!m_intIn || !m_intOut) return; bool somethingSelected = (m_grab_point_index >= 0); m_intIn->setEnabled(somethingSelected); m_intOut->setEnabled(somethingSelected); if (m_grab_point_index >= 0) { m_intIn->blockSignals(true); m_intOut->blockSignals(true); m_intIn->setValue(sp2io(m_curve.points()[m_grab_point_index].x(), m_inMin, m_inMax)); m_intOut->setValue(sp2io(m_curve.points()[m_grab_point_index].y(), m_outMin, m_outMax)); m_intIn->blockSignals(false); m_intOut->blockSignals(false); } else { /*FIXME: Ideally, these controls should hide away now */ } } void KisCurveWidget::Private::setCurveModified(bool rewriteSpinBoxesValues = true) { - if (rewriteSpinBoxesValues) { syncIOControls(); } m_splineDirty = true; m_curveWidget->update(); - m_curveWidget->emit modified(); + m_curveWidget->emit compressorShouldEmitModified(); } void KisCurveWidget::Private::setCurveRepaint() { m_curveWidget->update(); } void KisCurveWidget::Private::setState(enumState st) { m_state = st; } enumState KisCurveWidget::Private::state() const { return m_state; } #endif /* _KIS_CURVE_WIDGET_P_H_ */ diff --git a/libs/widgets/KisGradientSlider.cpp b/libs/widgets/KisGradientSlider.cpp index a5fa8dd422..f79da4aaa7 100644 --- a/libs/widgets/KisGradientSlider.cpp +++ b/libs/widgets/KisGradientSlider.cpp @@ -1,402 +1,402 @@ /* * This file is part of Krita * * Copyright (c) 2006 Frederic Coiffier * * 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. */ // Local includes. #include "KisGradientSlider.h" // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #define MARGIN 5 #define HANDLE_SIZE 10 KisGradientSlider::KisGradientSlider(QWidget *parent) : QWidget(parent) , m_leftmost(0) , m_rightmost(0) , m_scalingFactor(0) , m_blackCursor(0) , m_whiteCursor(0) , m_gammaCursor(0) , m_black(0) , m_white(255) , m_gamma(1.0) , m_gammaEnabled(false) , m_whiteEnabled(true) , m_feedback(false) , m_inverted(false) { m_grabCursor = None; setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); } KisGradientSlider::~KisGradientSlider() { } int KisGradientSlider::black() const { return m_black; } int KisGradientSlider::white() const { return m_white; } void KisGradientSlider::paintEvent(QPaintEvent *e) { QWidget::paintEvent(e); int x = 0; int y = 0; int wWidth = width() - (2 * MARGIN); int wHeight = height(); const int gradientHeight = qRound((double)wHeight / 7.0 * 2); QPainter p1(this); p1.fillRect(rect(), palette().background()); p1.setPen(Qt::black); p1.drawRect(MARGIN, MARGIN, wWidth, height() - 2 * MARGIN - HANDLE_SIZE); // Draw first gradient QLinearGradient grayGradient(MARGIN, y, wWidth, gradientHeight); grayGradient.setColorAt(0, m_inverted ? Qt::white : Qt::black); grayGradient.setColorAt(1, m_inverted ? Qt::black : Qt::white); p1.fillRect(MARGIN, 0, wWidth, gradientHeight, QBrush(grayGradient)); // Draw second gradient y = gradientHeight; p1.fillRect(MARGIN, y, wWidth, gradientHeight, Qt::white); if (m_blackCursor > 0 && !m_inverted) { p1.fillRect(MARGIN, y, m_blackCursor, gradientHeight, Qt::black); } else if (m_blackCursor < wWidth && m_inverted) { p1.fillRect(MARGIN + m_blackCursor, y, wWidth - m_blackCursor, gradientHeight, Qt::black); } int left = qMin(m_blackCursor, m_whiteCursor); int right = qMax(m_blackCursor, m_whiteCursor); for (x = left; x <= right; ++x) { double inten = (double)(x - m_blackCursor) / (double)(m_whiteCursor - m_blackCursor); inten = pow(inten, (1.0 / m_gamma)); int gray = (int)(255 * inten); p1.setPen(QColor(gray, gray, gray)); p1.drawLine(x + MARGIN, y, x + MARGIN, y + gradientHeight - 1); } // Draw cursors y += gradientHeight; QPoint a[3]; p1.setPen(Qt::darkGray); p1.setRenderHint(QPainter::Antialiasing, true); const int cursorHalfBase = (int)(gradientHeight / 1.5); a[0] = QPoint(m_blackCursor + MARGIN, y); a[1] = QPoint(m_blackCursor + MARGIN + cursorHalfBase, wHeight - 1); a[2] = QPoint(m_blackCursor + MARGIN - cursorHalfBase, wHeight - 1); p1.setBrush(Qt::black); p1.drawPolygon(a, 3); p1.setPen(Qt::black); if (m_gammaEnabled) { a[0] = QPoint(m_gammaCursor + MARGIN, y); a[1] = QPoint(m_gammaCursor + MARGIN + cursorHalfBase, wHeight - 1); a[2] = QPoint(m_gammaCursor + MARGIN - cursorHalfBase, wHeight - 1); p1.setBrush(Qt::gray); p1.drawPolygon(a, 3); } if (m_whiteEnabled) { a[0] = QPoint(m_whiteCursor + MARGIN, y); a[1] = QPoint(m_whiteCursor + MARGIN + cursorHalfBase, wHeight - 1); a[2] = QPoint(m_whiteCursor + MARGIN - cursorHalfBase, wHeight - 1); p1.setBrush(Qt::white); p1.drawPolygon(a, 3); } } void KisGradientSlider::resizeEvent(QResizeEvent *) { m_scalingFactor = (double)(width() - 2 * MARGIN) / 255; calculateCursorPositions(); update(); } void KisGradientSlider::mousePressEvent(QMouseEvent *e) { - eCursor closest_cursor; + eCursor closest_cursor = KisGradientSlider::None; int distance; if (e->button() != Qt::LeftButton) return; unsigned int x = e->pos().x(); int xMinusMargin = x - MARGIN; distance = width() + 1; // just a big number if (abs((int)(xMinusMargin - m_blackCursor)) < distance) { distance = abs((int)(xMinusMargin - m_blackCursor)); closest_cursor = BlackCursor; } if (abs((int)(xMinusMargin - m_whiteCursor)) < distance) { distance = abs((int)(xMinusMargin - m_whiteCursor)); closest_cursor = WhiteCursor; } if (m_gammaEnabled) { int gammaDistance = (int)xMinusMargin - m_gammaCursor; if (abs(gammaDistance) < distance) { distance = abs((int)xMinusMargin - m_gammaCursor); closest_cursor = GammaCursor; } else if (abs(gammaDistance) == distance) { if ((closest_cursor == BlackCursor) && (gammaDistance > 0)) { distance = abs(gammaDistance); closest_cursor = GammaCursor; } else if ((closest_cursor == WhiteCursor) && (gammaDistance < 0)) { distance = abs(gammaDistance); closest_cursor = GammaCursor; } } } if (distance > 20) { m_grabCursor = None; return; } // Determine cursor values and the leftmost and rightmost points. switch (closest_cursor) { case BlackCursor: m_blackCursor = xMinusMargin; m_grabCursor = closest_cursor; if (m_inverted) { m_leftmost = m_whiteCursor + 1; m_rightmost = width() - 2 * MARGIN - 1; } else { m_leftmost = 0; m_rightmost = m_whiteCursor - 1; } if (m_gammaEnabled) m_gammaCursor = calculateGammaCursor(); break; case WhiteCursor: m_whiteCursor = xMinusMargin; m_grabCursor = closest_cursor; if (m_inverted) { m_leftmost = 0; m_rightmost = m_blackCursor - 1; } else { m_leftmost = m_blackCursor + 1; m_rightmost = width() - 2 * MARGIN - 1; } if (m_gammaEnabled) m_gammaCursor = calculateGammaCursor(); break; case GammaCursor: m_gammaCursor = x; m_grabCursor = closest_cursor; m_leftmost = qMin(m_blackCursor, m_whiteCursor); m_rightmost = qMax(m_blackCursor, m_whiteCursor); { double delta = (double)(m_whiteCursor - m_blackCursor) / 2.0; double mid = (double)m_blackCursor + delta + MARGIN; double tmp = (xMinusMargin - mid) / delta; m_gamma = 1.0 / pow(10, tmp); } break; default: break; } update(); } void KisGradientSlider::mouseReleaseEvent(QMouseEvent * e) { if (e->button() != Qt::LeftButton) return; update(); switch (m_grabCursor) { case BlackCursor: m_black = qRound(m_blackCursor / m_scalingFactor); m_feedback = true; emit sigModifiedBlack(m_black); break; case WhiteCursor: m_white = qRound(m_whiteCursor / m_scalingFactor); m_feedback = true; emit sigModifiedWhite(m_white); break; case GammaCursor: emit sigModifiedGamma(m_gamma); break; default: break; } m_grabCursor = None; m_feedback = false; } void KisGradientSlider::mouseMoveEvent(QMouseEvent * e) { int x = e->pos().x(); int xMinusMargin = x - MARGIN; if (m_grabCursor != None) { // Else, drag the selected point if (xMinusMargin <= m_leftmost) xMinusMargin = m_leftmost; if (xMinusMargin >= m_rightmost) xMinusMargin = m_rightmost; switch (m_grabCursor) { case BlackCursor: if (m_blackCursor != xMinusMargin) { m_blackCursor = xMinusMargin; if (m_gammaEnabled) { m_gammaCursor = calculateGammaCursor(); } } break; case WhiteCursor: if (m_whiteCursor != xMinusMargin) { m_whiteCursor = xMinusMargin; if (m_gammaEnabled) { m_gammaCursor = calculateGammaCursor(); } } break; case GammaCursor: if (m_gammaCursor != xMinusMargin) { m_gammaCursor = xMinusMargin; double delta = (double)(m_whiteCursor - m_blackCursor) / 2.0; double mid = (double)m_blackCursor + delta; double tmp = (xMinusMargin - mid) / delta; m_gamma = 1.0 / pow(10, tmp); } break; default: break; } } update(); } void KisGradientSlider::calculateCursorPositions() { m_blackCursor = qRound(m_black * m_scalingFactor); m_whiteCursor = qRound(m_white * m_scalingFactor); m_gammaCursor = calculateGammaCursor(); } unsigned int KisGradientSlider::calculateGammaCursor() { double delta = (double)(m_whiteCursor - m_blackCursor) / 2.0; double mid = (double)m_blackCursor + delta; double tmp = log10(1.0 / m_gamma); return (unsigned int)qRound(mid + delta * tmp); } void KisGradientSlider::enableGamma(bool b) { m_gammaEnabled = b; update(); } double KisGradientSlider::getGamma(void) { return m_gamma; } void KisGradientSlider::enableWhite(bool b) { m_whiteEnabled = b; update(); } void KisGradientSlider::setInverted(bool b) { m_inverted = b; update(); } void KisGradientSlider::slotModifyBlack(int v) { if ((m_inverted && (v < m_white || v > width())) || (!m_inverted && (v < 0 || v > m_white)) || m_feedback) return; m_black = v; m_blackCursor = qRound(m_black * m_scalingFactor); m_gammaCursor = calculateGammaCursor(); update(); } void KisGradientSlider::slotModifyWhite(int v) { if ((m_inverted && (v < 0 || v > m_white)) || (!m_inverted && (v < m_black && v > width())) || m_feedback) return; m_white = v; m_whiteCursor = qRound(m_white * m_scalingFactor); m_gammaCursor = calculateGammaCursor(); update(); } void KisGradientSlider::slotModifyGamma(double v) { if (m_gamma != v) { emit sigModifiedGamma(v); } m_gamma = v; m_gammaCursor = calculateGammaCursor(); update(); } diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.cpp b/libs/widgets/KisVisualEllipticalSelectorShape.cpp index f6e7ed63fc..c69dc4e42d 100644 --- a/libs/widgets/KisVisualEllipticalSelectorShape.cpp +++ b/libs/widgets/KisVisualEllipticalSelectorShape.cpp @@ -1,283 +1,283 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KisVisualEllipticalSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualEllipticalSelectorShape::KisVisualEllipticalSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int barWidth, singelDTypes d) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualEllipticalSelectorShape" << this; m_type = d; m_barWidth = barWidth; } KisVisualEllipticalSelectorShape::~KisVisualEllipticalSelectorShape() { //qDebug() << "deleting KisVisualEllipticalSelectorShape" << this; } QSize KisVisualEllipticalSelectorShape::sizeHint() const { return QSize(180,180); } void KisVisualEllipticalSelectorShape::setBorderWidth(int width) { m_barWidth = width; forceImageUpdate(); update(); } QRect KisVisualEllipticalSelectorShape::getSpaceForSquare(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(135); QPointF tl = radius.p2(); radius.setAngle(315); QPointF br = radius.p2(); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForCircle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QPointF tl = QPointF (b.topLeft().x()+m_barWidth, b.topLeft().y()+m_barWidth); QPointF br = QPointF (b.bottomRight().x()-m_barWidth, b.bottomRight().y()-m_barWidth); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForTriangle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(90);//point at yellowgreen :) QPointF t = radius.p2(); radius.setAngle(330);//point to purple :) QPointF br = radius.p2(); radius.setAngle(210);//point to cerulean :) QPointF bl = radius.p2(); QPointF tl = QPoint(bl.x(),t.y()); QRect r(tl.toPoint(), br.toPoint()); return r; } QPointF KisVisualEllipticalSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { qreal x; qreal y; qreal offset=7.0; qreal a = (qreal)width()*0.5; QPointF center(a, a); QLineF line(center, QPoint((m_barWidth*0.5),a)); qreal angle = coordinate.x()*360.0; angle = fmod(angle+180.0,360.0); angle = 180.0-angle; angle = angle+180.0; if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { angle = (coordinate.x()/2)*360.0; angle = fmod((angle+270.0), 360.0); } line.setAngle(angle); if (getDimensions()!=KisVisualColorSelectorShape::onedimensional) { line.setLength(qMin(coordinate.y()*(a-offset), a-offset)); } x = qRound(line.p2().x()); y = qRound(line.p2().y()); return QPointF(x,y); } QPointF KisVisualEllipticalSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const { //default implementation: qreal x = 0.5; qreal y = 1.0; qreal offset = 7.0; QPointF center = QRectF(QPointF(0.0, 0.0), this->size()).center(); - qreal a = (this->width()/2); + qreal a = (qreal(this->width()) / qreal(2)); qreal xRel = center.x()-coordinate.x(); qreal yRel = center.y()-coordinate.y(); qreal radius = sqrt(xRel*xRel+yRel*yRel); if (m_type!=KisVisualEllipticalSelectorShape::borderMirrored){ qreal angle = atan2(yRel, xRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+360, 360.0); x = angle/360.0; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,radius/(a-offset), 1.0); } } else { qreal angle = atan2(xRel, yRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+180, 360.0); if (angle>180.0) { angle = 360.0-angle; } x = (angle/360.0)*2; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,(radius+offset)/a, 1.0); } } return QPointF(x, y); } QRegion KisVisualEllipticalSelectorShape::getMaskMap() { QRegion mask = QRegion(0,0,width(),height(), QRegion::Ellipse); if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2), QRegion::Ellipse)); } return mask; } QImage KisVisualEllipticalSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const { const KisVisualColorSelector *selector = qobject_cast(parent()); Q_ASSERT(selector); // optimization assumes widget is (close to) square, but should still render correctly as ellipse int rMaxSquare = qRound(qMax(width(), height()) * 0.5f + 0.5f); rMaxSquare *= rMaxSquare; int rMinSquare = 0; if (getDimensions() == Dimensions::onedimensional) { rMinSquare = qMax(0, qRound(qMin(width(), height()) * 0.5f - m_barWidth)); rMinSquare *= rMinSquare; } int cx = width()/2; int cy = height()/2; // Fill a buffer with the right kocolors quint32 imageSize = width() * height() * pixelSize; QScopedArrayPointer raw(new quint8[imageSize] {}); quint8 *dataPtr = raw.data(); bool is2D = (getDimensions() == Dimensions::twodimensional); QVector4D coordinates = channelValues; QVector channels = getChannels(); for (int y = 0; y < height(); y++) { int dy = y - cy; for (int x=0; x < width(); x++) { int dx = x - cx; int radSquare = dx*dx + dy*dy; if (radSquare >= rMinSquare && radSquare < rMaxSquare) { QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPoint(x, y)); coordinates[channels.at(0)] = newcoordinate.x(); if (is2D){ coordinates[channels.at(1)] = newcoordinate.y(); } KoColor c = selector->convertShapeCoordsToKoColor(coordinates); memcpy(dataPtr, c.data(), pixelSize); } dataPtr += pixelSize; } } QImage image = convertImageMap(raw.data(), imageSize); // cleanup edges by erasing with antialiased circles QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.setCompositionMode(QPainter::CompositionMode_Clear); QPen pen; pen.setWidth(5); painter.setPen(pen); painter.drawEllipse(QRect(0,0,width(),height())); if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); painter.setBrush(Qt::SolidPattern); painter.drawEllipse(innerRect); } return image; } void KisVisualEllipticalSelectorShape::drawCursor() { //qDebug() << this << "KisVisualEllipticalSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); } painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualTriangleSelectorShape.cpp b/libs/widgets/KisVisualTriangleSelectorShape.cpp index 18cc17c0f1..39c60fe0bf 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.cpp +++ b/libs/widgets/KisVisualTriangleSelectorShape.cpp @@ -1,205 +1,205 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KisVisualTriangleSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualTriangleSelectorShape::KisVisualTriangleSelectorShape(QWidget *parent, - Dimensions dimension, - const KoColorSpace *cs, - int channel1, int channel2, - const KoColorDisplayRendererInterface *displayRenderer, - int barwidth) + Dimensions dimension, + const KoColorSpace *cs, + int channel1, int channel2, + const KoColorDisplayRendererInterface *displayRenderer, + int barwidth) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualTriangleSelectorShape" << this; m_barWidth = barwidth; setTriangle(); } KisVisualTriangleSelectorShape::~KisVisualTriangleSelectorShape() { //qDebug() << "deleting KisVisualTriangleSelectorShape" << this; } void KisVisualTriangleSelectorShape::setBorderWidth(int width) { m_barWidth = width; } QRect KisVisualTriangleSelectorShape::getSpaceForSquare(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForCircle(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForTriangle(QRect geom) { return geom; } void KisVisualTriangleSelectorShape::setTriangle() { QPoint apex = QPoint (width()*0.5,0); QPolygon triangle; triangle<< QPoint(0,height()) << apex << QPoint(width(),height()) << QPoint(0,height()); m_triangle = triangle; QLineF a(triangle.at(0),triangle.at(1)); QLineF b(triangle.at(0),triangle.at(2)); QLineF ap(triangle.at(2), a.pointAt(0.5)); QLineF bp(triangle.at(1), b.pointAt(0.5)); QPointF intersect; ap.intersect(bp,&intersect); m_center = intersect; QLineF r(triangle.at(0), intersect); m_radius = r.length(); } QPointF KisVisualTriangleSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { qreal offset=7.0;//the offset is so we get a nice little border that allows selecting extreme colors better. qreal yOffset = (cos(kisDegreesToRadians(30))*offset)*2; qreal xOffset = qFloor(sin(kisDegreesToRadians(30))*offset); qreal y = qMax(qMin((coordinate.y()*(height()-yOffset-offset))+yOffset, (qreal)height()-offset),yOffset); qreal triWidth = width(); qreal horizontalLineLength = ((y-yOffset)*(2./sqrt(3.))); qreal horizontalLineStart = (triWidth*0.5)-(horizontalLineLength*0.5); qreal relativeX = qMin(coordinate.x()*(horizontalLineLength), horizontalLineLength); qreal x = qMax(relativeX + horizontalLineStart + xOffset, horizontalLineStart+xOffset); if (y<=yOffset){ x = 0.5*width(); } return QPointF(x,y); } QPointF KisVisualTriangleSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const { //default implementation: gotten from the kotrianglecolorselector/kis_color_selector_triangle. qreal x = 0.5; qreal y = 0.5; qreal offset=7.0; //the offset is so we get a nice little border that allows selecting extreme colors better. qreal yOffset = (cos(kisDegreesToRadians(30))*offset)*2; qreal xOffset = qFloor(sin(kisDegreesToRadians(30))*offset); y = qMin(qMax((qreal)coordinate.y()-yOffset, 0.0)/(height()-yOffset-offset), 1.0); qreal triWidth = width(); qreal horizontalLineLength = ((qreal)coordinate.y()-yOffset)*(2./sqrt(3.)); qreal horizontalLineStart = (triWidth*0.5)-(horizontalLineLength*0.5); qreal relativeX = qMax((qreal)coordinate.x()-xOffset-horizontalLineStart,0.0); x = qMin(relativeX/horizontalLineLength, 1.0); if (coordinate.y()<=yOffset){ x = 0.5; } return QPointF(x, y); } QRegion KisVisualTriangleSelectorShape::getMaskMap() { QRegion mask = QRegion(m_triangle); //QRegion mask = QRegion(); //if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { // mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2))); //} return mask; } void KisVisualTriangleSelectorShape::resizeEvent(QResizeEvent *e) { //qDebug() << this << "KisVisualTriangleSelectorShape::resizeEvent(QResizeEvent *)"; setTriangle(); KisVisualColorSelectorShape::resizeEvent(e); } void KisVisualTriangleSelectorShape::drawCursor() { //qDebug() << this << "KisVisualTriangleSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); painter.save(); painter.setCompositionMode(QPainter::CompositionMode_Clear); QPen pen; pen.setWidth(10); painter.setPen(pen); painter.drawPolygon(m_triangle); painter.restore(); //QPainterPath path; QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; //QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); /*if(m_type==KisVisualTriangleSelectorShape::borderMirrored){ painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else {*/ - painter.setPen(Qt::white); - fill.setColor(Qt::white); - painter.setBrush(fill); - painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); - fill.setColor(col); - painter.setPen(Qt::black); - painter.setBrush(fill); - painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); + painter.setPen(Qt::white); + fill.setColor(Qt::white); + painter.setBrush(fill); + painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); + fill.setColor(col); + painter.setPen(Qt::black); + painter.setBrush(fill); + painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); //} painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualTriangleSelectorShape.h b/libs/widgets/KisVisualTriangleSelectorShape.h index 8e7ce92526..9d4f052702 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.h +++ b/libs/widgets/KisVisualTriangleSelectorShape.h @@ -1,76 +1,74 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #define KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualTriangleSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: - enum singelDTypes{border, borderMirrored}; explicit KisVisualTriangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int barwidth=20 ); ~KisVisualTriangleSelectorShape() override; void setBorderWidth(int width) override; void setTriangle(); /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: void resizeEvent(QResizeEvent *e) override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const override; - singelDTypes m_type; int m_barWidth; QPolygon m_triangle; QPointF m_center; qreal m_radius; QRegion getMaskMap() override; void drawCursor() override; }; #endif diff --git a/libs/widgets/KoConfigAuthorPage.cpp b/libs/widgets/KoConfigAuthorPage.cpp index ea7ced67b6..2d8db17cb0 100644 --- a/libs/widgets/KoConfigAuthorPage.cpp +++ b/libs/widgets/KoConfigAuthorPage.cpp @@ -1,441 +1,441 @@ /* This file is part of the KDE project Copyright (c) 2000 Simon Hausmann 2006 Martin Pfeiffer 2012 C. Boemann 2017 Wolthera van Hövell tot Westerflier 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 "KoConfigAuthorPage.h" #include "ui_KoConfigAuthorPage.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 class Q_DECL_HIDDEN KoConfigAuthorPage::Private { public: QList profileUiList; QStackedWidget *stack; QComboBox *cmbAuthorProfiles; QToolButton *bnDeleteUser; QStringList positions; QStringList contactModes; QStringList contactKeys; QString defaultAuthor; }; KoConfigAuthorPage::KoConfigAuthorPage() : d(new Private) { QGridLayout *layout = new QGridLayout; d->cmbAuthorProfiles = new QComboBox(); layout->addWidget(d->cmbAuthorProfiles, 0, 0); QToolButton *newUser = new QToolButton(); newUser->setIcon(koIcon("list-add")); newUser->setToolTip(i18n("Add new author profile (starts out as a copy of current)")); layout->addWidget(newUser, 0, 1); d->bnDeleteUser = new QToolButton(); d->bnDeleteUser->setIcon(koIcon("trash-empty")); d->bnDeleteUser->setToolTip(i18n("Delete the author profile")); layout->addWidget(d->bnDeleteUser, 0, 2); QFrame *f = new QFrame(); f->setFrameStyle(QFrame::HLine | QFrame::Sunken); layout->addWidget(f, 1, 0); d->stack = new QStackedWidget(); layout->addWidget(d->stack, 2, 0, 1, 3); setLayout(layout); //list of positions that we can use to provide useful autocompletion. d->positions << QString(i18nc("This is a list of suggestions for positions an artist can take, comma-separated","Adapter,Animator,Artist,Art Director,Author,Assistant," "Editor,Background,Cartoonist,Colorist,Concept Artist," "Corrector,Cover Artist,Creator,Designer,Inker," "Letterer,Matte Painter,Painter,Penciller,Proofreader," "Pixel Artist,Redliner,Sprite Artist,Typographer,Texture Artist," "Translator,Writer,Other")).split(","); //Keep these two in sync! d->contactModes << i18n("Homepage") << i18n("Email") << i18n("Post Address") << i18n("Telephone") << i18n("Fax"); d->contactKeys << "homepage" << "email" << "address" << "telephone" << "fax"; QStringList headerlabels; headerlabels<< i18n("Type") << i18n("Entry"); - Ui::KoConfigAuthorPage *aUi = new Ui::KoConfigAuthorPage(); + Ui::KoConfigAuthorPage *aUi = 0; QWidget *w = new QWidget; d->defaultAuthor = i18n("Anonymous"); QStringList profilesNew; QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"); QStringList filters = QStringList() << "*.authorinfo"; Q_FOREACH(const QString &entry, dir.entryList(filters)) { QFile file(dir.absoluteFilePath(entry)); if (file.exists()) { file.open(QFile::ReadOnly); QByteArray ba = file.readAll(); file.close(); QDomDocument doc = QDomDocument(); doc.setContent(ba); QDomElement root = doc.firstChildElement(); aUi = new Ui::KoConfigAuthorPage(); w = new QWidget; aUi->setupUi(w); QString profile = root.attribute("name"); QDomElement el = root.firstChildElement("nickname"); if (!el.isNull()) { aUi->leNickName->setText(el.text()); } el = root.firstChildElement("givenname"); if (!el.isNull()) { aUi->leFirstName->setText(el.text()); } el = root.firstChildElement("middlename"); if (!el.isNull()) { aUi->leInitials->setText(el.text()); } el = root.firstChildElement("familyname"); if (!el.isNull()) { aUi->leLastName->setText(el.text()); } el = root.firstChildElement("title"); if (!el.isNull()) { aUi->leTitle->setText(el.text()); } el = root.firstChildElement("position"); if (!el.isNull()) { aUi->lePosition->setText(el.text()); } el = root.firstChildElement("company"); if (!el.isNull()) { aUi->leCompany->setText(el.text()); } aUi->tblContactInfo->setItemDelegate(new KoContactInfoDelegate(this, d->contactModes)); QStandardItemModel *modes = new QStandardItemModel(); aUi->tblContactInfo->setModel(modes); el = root.firstChildElement("contact"); while (!el.isNull()) { QList list; QString type = d->contactModes.at(d->contactKeys.indexOf(el.attribute("type"))); list.append(new QStandardItem(type)); list.append(new QStandardItem(el.text())); modes->appendRow(list); el = el.nextSiblingElement("contact"); } modes->setHorizontalHeaderLabels(headerlabels); QCompleter *positionSuggestions = new QCompleter(d->positions); positionSuggestions->setCaseSensitivity(Qt::CaseInsensitive); aUi->lePosition->setCompleter(positionSuggestions); connect(aUi->btnAdd, SIGNAL(clicked()), this, SLOT(addContactEntry())); connect(aUi->btnRemove, SIGNAL(clicked()), this, SLOT(removeContactEntry())); d->cmbAuthorProfiles->addItem(profile); profilesNew.append(profile); d->profileUiList.append(aUi); d->stack->addWidget(w); } } // Add all the user defined profiles (old type) KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); foreach (const QString &profile , profiles) { if (!profilesNew.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); aUi = new Ui::KoConfigAuthorPage(); w = new QWidget; aUi->setupUi(w); aUi->leNickName->setText(cgs.readEntry("creator")); aUi->leFirstName->setText(cgs.readEntry("creator-first-name")); aUi->leLastName->setText(cgs.readEntry("creator-last-name")); aUi->leInitials->setText(cgs.readEntry("initial")); aUi->leTitle->setText(cgs.readEntry("author-title")); aUi->lePosition->setText(cgs.readEntry("position")); QCompleter *positionSuggestions = new QCompleter(d->positions); positionSuggestions->setCaseSensitivity(Qt::CaseInsensitive); aUi->lePosition->setCompleter(positionSuggestions); aUi->leCompany->setText(cgs.readEntry("company")); aUi->tblContactInfo->setItemDelegate(new KoContactInfoDelegate(this, d->contactModes)); QStandardItemModel *modes = new QStandardItemModel(); aUi->tblContactInfo->setModel(modes); if (cgs.hasKey("email")) { QList list; QString email = d->contactModes.at(d->contactKeys.indexOf("email")); list.append(new QStandardItem(email)); list.append(new QStandardItem(cgs.readEntry("email"))); modes->appendRow(list); } if (cgs.hasKey("telephone-work")) { QList list; QString tel = d->contactModes.at(d->contactKeys.indexOf("telephone")); list.append(new QStandardItem(tel)); list.append(new QStandardItem(cgs.readEntry("telephone-work"))); modes->appendRow(list); } if (cgs.hasKey("fax")) { QList list; QString fax = d->contactModes.at(d->contactKeys.indexOf("fax")); list.append(new QStandardItem(fax)); list.append(new QStandardItem(cgs.readEntry("fax"))); modes->appendRow(list); } QStringList postal; postal << cgs.readEntry("street") << cgs.readEntry("postal-code") << cgs.readEntry("city") << cgs.readEntry("country"); QString address; Q_FOREACH(QString part, postal) { if (!part.isEmpty()) { address+= part + "\n"; } } if (!address.isEmpty()) { QList list; QString add = d->contactModes.at(d->contactKeys.indexOf("address")); list.append(new QStandardItem(add)); list.append(new QStandardItem(address)); modes->appendRow(list); } modes->setHorizontalHeaderLabels(headerlabels); connect(aUi->btnAdd, SIGNAL(clicked()), this, SLOT(addContactEntry())); connect(aUi->btnRemove, SIGNAL(clicked()), this, SLOT(removeContactEntry())); d->cmbAuthorProfiles->addItem(profile); d->profileUiList.append(aUi); d->stack->addWidget(w); } } // Add a default profile aUi = new Ui::KoConfigAuthorPage(); w = new QWidget; if (!profiles.contains(d->defaultAuthor) || profilesNew.contains(d->defaultAuthor)) { //w->setEnabled(false); aUi->setupUi(w); w->setEnabled(false); d->cmbAuthorProfiles->insertItem(0, d->defaultAuthor); d->stack->insertWidget(0, w); d->profileUiList.insert(0, aUi); } // Connect slots connect(d->cmbAuthorProfiles, SIGNAL(currentIndexChanged(int)), this, SLOT(profileChanged(int))); connect(newUser, SIGNAL(clicked(bool)), this, SLOT(addUser())); connect(d->bnDeleteUser, SIGNAL(clicked(bool)), this, SLOT(deleteUser())); d->cmbAuthorProfiles->setCurrentIndex(0); profileChanged(0); } KoConfigAuthorPage::~KoConfigAuthorPage() { delete d; } void KoConfigAuthorPage::profileChanged(int i) { d->stack->setCurrentIndex(i); // Profile 0 should never be deleted: it's the anonymous profile. d->bnDeleteUser->setEnabled(i > 0); } void KoConfigAuthorPage::addUser() { bool ok; QString profileName = QInputDialog::getText(this, i18n("Name of Profile"), i18n("Name (not duplicate or blank name):"), QLineEdit::Normal, "", &ok); if (!ok) { return; } Ui::KoConfigAuthorPage *curUi = d->profileUiList[d->cmbAuthorProfiles->currentIndex()]; Ui::KoConfigAuthorPage *aUi = new Ui::KoConfigAuthorPage(); QWidget *w = new QWidget; aUi->setupUi(w); aUi->leNickName->setText(curUi->leNickName->text()); aUi->leInitials->setText(curUi->leInitials->text()); aUi->leTitle->setText(curUi->leTitle->text()); aUi->leCompany->setText(curUi->leCompany->text()); aUi->leFirstName->setText(curUi->leFirstName->text()); aUi->leLastName->setText(curUi->leLastName->text()); aUi->lePosition->setText(curUi->lePosition->text()); QCompleter *positionSuggestions = new QCompleter(d->positions); positionSuggestions->setCaseSensitivity(Qt::CaseInsensitive); aUi->lePosition->setCompleter(positionSuggestions); aUi->tblContactInfo->setItemDelegate(new KoContactInfoDelegate(this, d->contactModes)); QStandardItemModel *modes = new QStandardItemModel(); aUi->tblContactInfo->setModel(modes); connect(aUi->btnAdd, SIGNAL(clicked()), this, SLOT(addContactEntry())); connect(aUi->btnRemove, SIGNAL(clicked()), this, SLOT(removeContactEntry())); int index = d->cmbAuthorProfiles->currentIndex() + 1; d->cmbAuthorProfiles->insertItem(index, profileName); d->profileUiList.insert(index, aUi); d->stack->insertWidget(index, w); d->cmbAuthorProfiles->setCurrentIndex(index); } void KoConfigAuthorPage::deleteUser() { int index = d->cmbAuthorProfiles->currentIndex(); QWidget *w = d->stack->currentWidget(); d->stack->removeWidget(w); d->profileUiList.removeAt(index); d->cmbAuthorProfiles->removeItem(index); delete w; } void KoConfigAuthorPage::addContactEntry() { int i = d->cmbAuthorProfiles->currentIndex(); Ui::KoConfigAuthorPage *aUi = d->profileUiList[i]; QStandardItemModel *contact = static_cast(aUi->tblContactInfo->model()); QListlist; list.append(new QStandardItem(d->contactModes.at(0))); list.append(new QStandardItem(i18n("New Contact Info"))); contact->appendRow(list); aUi->tblContactInfo->setModel(contact); } void KoConfigAuthorPage::removeContactEntry() { int i = d->cmbAuthorProfiles->currentIndex(); Ui::KoConfigAuthorPage *aUi = d->profileUiList[i]; QModelIndex index = aUi->tblContactInfo->selectionModel()->currentIndex(); aUi->tblContactInfo->model()->removeRow(index.row()); } void KoConfigAuthorPage::apply() { QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"; QDir dir(authorInfo); if (!dir.mkpath(authorInfo)) { qWarning()<<"We can't make an author info directory, and therefore not save!"; return; } for (int i = 0; i < d->profileUiList.size(); i++) { if (d->cmbAuthorProfiles->itemText(i)!= d->defaultAuthor) { QByteArray ba; QDomDocument doc = QDomDocument(); Ui::KoConfigAuthorPage *aUi = d->profileUiList[i]; QDomElement root = doc.createElement("author"); root.setAttribute("name", d->cmbAuthorProfiles->itemText(i)); QDomElement nickname = doc.createElement("nickname"); nickname.appendChild(doc.createTextNode(aUi->leNickName->text())); root.appendChild(nickname); QDomElement givenname = doc.createElement("givenname"); givenname.appendChild(doc.createTextNode(aUi->leFirstName->text())); root.appendChild(givenname); QDomElement familyname = doc.createElement("familyname"); familyname.appendChild(doc.createTextNode(aUi->leLastName->text())); root.appendChild(familyname); QDomElement middlename = doc.createElement("middlename"); middlename.appendChild(doc.createTextNode(aUi->leInitials->text())); root.appendChild(middlename); QDomElement title = doc.createElement("title"); title.appendChild(doc.createTextNode(aUi->leTitle->text())); root.appendChild(title); QDomElement company = doc.createElement("company"); company.appendChild(doc.createTextNode(aUi->leCompany->text())); root.appendChild(company); QDomElement position = doc.createElement("position"); position.appendChild(doc.createTextNode(aUi->lePosition->text())); root.appendChild(position); if (aUi->tblContactInfo) { if (aUi->tblContactInfo->model()) { for (int i=0; itblContactInfo->model()->rowCount(); i++) { QModelIndex index = aUi->tblContactInfo->model()->index(i, 1); QModelIndex typeIndex = aUi->tblContactInfo->model()->index(i, 0); QDomElement contactEl = doc.createElement("contact"); QString content = QVariant(aUi->tblContactInfo->model()->data(index)).toString(); contactEl.appendChild(doc.createTextNode(content)); QString type = QVariant(aUi->tblContactInfo->model()->data(typeIndex)).toString(); contactEl.setAttribute("type", d->contactKeys.at(d->contactModes.indexOf(type))); root.appendChild(contactEl); } } } doc.appendChild(root); ba = doc.toByteArray(); QFile f(authorInfo + d->cmbAuthorProfiles->itemText(i) +".authorinfo"); f.open(QFile::WriteOnly); if (f.write(ba) < 0) { qWarning()<<"Writing author info went wrong:"< 0) { return new QLineEdit(parent); } else { QComboBox *box = new QComboBox(parent); box->addItems(m_contactModes); return box; } } diff --git a/libs/widgets/kis_color_input.cpp b/libs/widgets/kis_color_input.cpp index 3813ccfa45..01b7b0dd74 100644 --- a/libs/widgets/kis_color_input.cpp +++ b/libs/widgets/kis_color_input.cpp @@ -1,417 +1,436 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2011 Sven Langkamp * Copyright (c) 2015 Moritz Molch * * 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 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_input.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" KisColorInput::KisColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : QWidget(parent), m_channelInfo(channelInfo), m_color(color), m_displayRenderer(displayRenderer), m_usePercentage(usePercentage) { } void KisColorInput::init() { QHBoxLayout* m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0,0,0,0); m_layout->setSpacing(1); QLabel* m_label = new QLabel(i18n("%1:", m_channelInfo->name()), this); m_layout->addWidget(m_label); m_colorSlider = new KoColorSlider(Qt::Horizontal, this, m_displayRenderer); m_colorSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_layout->addWidget(m_colorSlider); QWidget* m_input = createInput(); m_colorSlider->setFixedHeight(m_input->sizeHint().height()); m_layout->addWidget(m_input); } KisIntegerColorInput::KisIntegerColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage) { init(); } void KisIntegerColorInput::setValue(int v) { quint8* data = m_color->data() + m_channelInfo->pos(); switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: *(reinterpret_cast(data)) = v; break; case KoChannelInfo::UINT16: *(reinterpret_cast(data)) = v; break; case KoChannelInfo::UINT32: *(reinterpret_cast(data)) = v; break; default: Q_ASSERT(false); } emit(updated()); } void KisIntegerColorInput::update() { KoColor min = *m_color; KoColor max = *m_color; quint8* data = m_color->data() + m_channelInfo->pos(); quint8* dataMin = min.data() + m_channelInfo->pos(); quint8* dataMax = max.data() + m_channelInfo->pos(); m_intNumInput->blockSignals(true); m_colorSlider->blockSignals(true); switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 255.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFF; break; case KoChannelInfo::UINT16: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 65535.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFFFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFFFF; break; case KoChannelInfo::UINT32: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 4294967295.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFFFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFFFFFFFF; break; default: Q_ASSERT(false); } m_colorSlider->setColors(min, max); m_intNumInput->blockSignals(false); m_colorSlider->blockSignals(false); } QWidget* KisIntegerColorInput::createInput() { m_intNumInput = new KisIntParseSpinBox(this); m_intNumInput->setMinimum(0); m_colorSlider->setMinimum(0); if (m_usePercentage) { m_intNumInput->setSuffix(i18n("%")); } else { m_intNumInput->setSuffix(""); } switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFF); } m_colorSlider->setMaximum(0xFF); break; case KoChannelInfo::UINT16: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFFFF); } m_colorSlider->setMaximum(0xFFFF); break; case KoChannelInfo::UINT32: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFFFFFFFF); } m_colorSlider->setMaximum(0xFFFFFFFF); break; default: Q_ASSERT(false); } connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(onColorSliderChanged(int))); connect(m_intNumInput, SIGNAL(valueChanged(int)), this, SLOT(onNumInputChanged(int))); return m_intNumInput; } void KisIntegerColorInput::setPercentageWise(bool val) { m_usePercentage = val; if (m_usePercentage) { m_intNumInput->setSuffix(i18n("%")); } else { m_intNumInput->setSuffix(""); } } void KisIntegerColorInput::onColorSliderChanged(int val) { m_intNumInput->blockSignals(true); if (m_usePercentage) { switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: m_intNumInput->setValue(round((val*1.0) / 255.0 * 100.0)); break; case KoChannelInfo::UINT16: m_intNumInput->setValue(round((val*1.0) / 65535.0 * 100.0)); break; case KoChannelInfo::UINT32: m_intNumInput->setValue(round((val*1.0) / 4294967295.0 * 100.0)); break; default: Q_ASSERT(false); } } else { m_intNumInput->setValue(val); } m_intNumInput->blockSignals(false); setValue(val); } void KisIntegerColorInput::onNumInputChanged(int val) { m_colorSlider->blockSignals(true); if (m_usePercentage) { switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: m_colorSlider->setValue((val*1.0)/100.0 * 255.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 255.0); break; case KoChannelInfo::UINT16: m_colorSlider->setValue((val*1.0)/100.0 * 65535.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 65535.0); break; case KoChannelInfo::UINT32: m_colorSlider->setValue((val*1.0)/100.0 * 4294967295.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 4294967295.0); break; default: Q_ASSERT(false); } } else { m_colorSlider->setValue(val); m_colorSlider->blockSignals(false); setValue(val); } } KisFloatColorInput::KisFloatColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage) { init(); } void KisFloatColorInput::setValue(double v) { quint8* data = m_color->data() + m_channelInfo->pos(); switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: *(reinterpret_cast(data)) = v; break; #endif case KoChannelInfo::FLOAT32: *(reinterpret_cast(data)) = v; break; default: Q_ASSERT(false); } emit(updated()); } QWidget* KisFloatColorInput::createInput() { m_dblNumInput = new KisDoubleParseSpinBox(this); m_dblNumInput->setMinimum(0); m_dblNumInput->setMaximum(1.0); connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); connect(m_dblNumInput, SIGNAL(valueChanged(double)), this, SLOT(setValue(double))); m_dblNumInput->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_dblNumInput->setMinimumWidth(60); m_dblNumInput->setMaximumWidth(60); + + quint8* data = m_color->data() + m_channelInfo->pos(); + qreal value = 1.0; + + switch (m_channelInfo->channelValueType()) { +#ifdef HAVE_OPENEXR + case KoChannelInfo::FLOAT16: + value = *(reinterpret_cast(data)); + break; +#endif + case KoChannelInfo::FLOAT32: + value = *(reinterpret_cast(data)); + break; + default: + Q_ASSERT(false); + } + m_dblNumInput->setValue(value); + return m_dblNumInput; } void KisFloatColorInput::sliderChanged(int i) { const qreal floatRange = m_maxValue - m_minValue; m_dblNumInput->setValue(m_minValue + (i / 255.0) * floatRange); } void KisFloatColorInput::update() { KoColor min = *m_color; KoColor max = *m_color; quint8* data = m_color->data() + m_channelInfo->pos(); quint8* dataMin = min.data() + m_channelInfo->pos(); quint8* dataMax = max.data() + m_channelInfo->pos(); qreal value = 1.0; m_minValue = m_displayRenderer->minVisibleFloatValue(m_channelInfo); m_maxValue = m_displayRenderer->maxVisibleFloatValue(m_channelInfo); + m_colorSlider->blockSignals(true); switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: value = *(reinterpret_cast(data)); m_minValue = qMin(value, m_minValue); m_maxValue = qMax(value, m_maxValue); *(reinterpret_cast(dataMin)) = m_minValue; *(reinterpret_cast(dataMax)) = m_maxValue; break; #endif case KoChannelInfo::FLOAT32: value = *(reinterpret_cast(data)); m_minValue = qMin(value, m_minValue); m_maxValue = qMax(value, m_maxValue); *(reinterpret_cast(dataMin)) = m_minValue; *(reinterpret_cast(dataMax)) = m_maxValue; break; default: Q_ASSERT(false); } m_dblNumInput->setMinimum(m_minValue); m_dblNumInput->setMaximum(m_maxValue); // ensure at least 3 significant digits are always shown int newPrecision = 2 + qMax(qreal(0.0), std::ceil(-std::log10(m_maxValue))); if (newPrecision != m_dblNumInput->decimals()) { m_dblNumInput->setDecimals(newPrecision); m_dblNumInput->updateGeometry(); } m_colorSlider->setColors(min, max); const qreal floatRange = m_maxValue - m_minValue; - m_dblNumInput->setValue(value); m_colorSlider->setValue((value - m_minValue) / floatRange * 255); + m_colorSlider->blockSignals(false); } KisHexColorInput::KisHexColorInput(QWidget* parent, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, 0, color, displayRenderer, usePercentage) { QHBoxLayout* m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0,0,0,0); m_layout->setSpacing(1); QLabel* m_label = new QLabel(i18n("Color name:"), this); m_label->setMinimumWidth(50); m_layout->addWidget(m_label); QWidget* m_input = createInput(); m_input->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_layout->addWidget(m_input); } void KisHexColorInput::setValue() { QString valueString = m_hexInput->text(); valueString.remove(QChar('#')); QList channels = m_color->colorSpace()->channels(); channels = KoChannelInfo::displayOrderSorted(channels); Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->channelType() == KoChannelInfo::COLOR) { Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8); quint8* data = m_color->data() + channel->pos(); int value = valueString.left(2).toInt(0, 16); *(reinterpret_cast(data)) = value; valueString.remove(0, 2); } } emit(updated()); } void KisHexColorInput::update() { QString hexString("#"); QList channels = m_color->colorSpace()->channels(); channels = KoChannelInfo::displayOrderSorted(channels); Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->channelType() == KoChannelInfo::COLOR) { Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8); quint8* data = m_color->data() + channel->pos(); hexString.append(QString("%1").arg(*(reinterpret_cast(data)), 2, 16, QChar('0'))); } } m_hexInput->setText(hexString); } QWidget* KisHexColorInput::createInput() { m_hexInput = new QLineEdit(this); m_hexInput->setAlignment(Qt::AlignRight); int digits = 2*m_color->colorSpace()->colorChannelCount(); QString pattern = QString("#?[a-fA-F0-9]{%1,%2}").arg(digits).arg(digits); m_hexInput->setValidator(new QRegExpValidator(QRegExp(pattern), this)); connect(m_hexInput, SIGNAL(editingFinished()), this, SLOT(setValue())); return m_hexInput; } diff --git a/libs/widgetutils/kis_double_parse_spin_box.cpp b/libs/widgetutils/kis_double_parse_spin_box.cpp index 9297a90e0b..50005053dd 100644 --- a/libs/widgetutils/kis_double_parse_spin_box.cpp +++ b/libs/widgetutils/kis_double_parse_spin_box.cpp @@ -1,242 +1,236 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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_double_parse_spin_box.h" #include "kis_num_parser.h" #include #include #include #include #include #include // for qIsNaN KisDoubleParseSpinBox::KisDoubleParseSpinBox(QWidget *parent) : QDoubleSpinBox(parent), boolLastValid(true), lastExprParsed(QStringLiteral("0.0")) { setAlignment(Qt::AlignRight); connect(this, SIGNAL(noMoreParsingError()), this, SLOT(clearErrorStyle())); //hack to let the clearError be called, even if the value changed method is the one from QDoubleSpinBox. connect(this, SIGNAL(valueChanged(double)), this, SLOT(clearError())); connect(this, SIGNAL(errorWhileParsing(QString)), this, SLOT(setErrorStyle())); oldValue = value(); warningIcon = new QLabel(this); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16)); } else { warningIcon->setText("!"); } warningIcon->setStyleSheet("background:transparent;"); warningIcon->move(1, 1); warningIcon->setVisible(false); isOldPaletteSaved = false; areOldMarginsSaved = false; } KisDoubleParseSpinBox::~KisDoubleParseSpinBox() { } double KisDoubleParseSpinBox::valueFromText(const QString & text) const { lastExprParsed = text; bool ok; double ret; if ( (suffix().isEmpty() || !text.endsWith(suffix())) && (prefix().isEmpty() || !text.startsWith(prefix())) ) { ret = KisNumericParser::parseSimpleMathExpr(text, &ok); } else { QString expr = text; if (text.endsWith(suffix())) { expr.remove(text.size()-suffix().size(), suffix().size()); } if(text.startsWith(prefix())){ expr.remove(0, prefix().size()); } lastExprParsed = expr; ret = KisNumericParser::parseSimpleMathExpr(expr, &ok); } if(qIsNaN(ret) || qIsInf(ret)){ ok = false; } if (!ok) { if (boolLastValid) { oldValue = value(); } boolLastValid = false; ret = oldValue; //in case of error set to minimum. } else { if (!boolLastValid) { oldValue = ret; } boolLastValid = true; } return ret; } QString KisDoubleParseSpinBox::textFromValue(double val) const { if (!boolLastValid) { emit errorWhileParsing(lastExprParsed); return lastExprParsed; } emit noMoreParsingError(); - double v = KisNumericParser::parseSimpleMathExpr(veryCleanText()); - v = QString("%1").arg(v, 0, 'f', decimals()).toDouble(); - if (hasFocus() && (v == value() || (v > maximum() && value() == maximum()) || (v < minimum() && value() == minimum())) ) { //solve a very annoying bug where the formula can collapse while editing. With this trick the formula is not lost until focus is lost. - return veryCleanText(); - } - return QDoubleSpinBox::textFromValue(val); } QString KisDoubleParseSpinBox::veryCleanText() const { return cleanText(); } QValidator::State KisDoubleParseSpinBox::validate ( QString & input, int & pos ) const { Q_UNUSED(input); Q_UNUSED(pos); return QValidator::Acceptable; } void KisDoubleParseSpinBox::stepBy(int steps) { boolLastValid = true; //reset to valid state so we can use the up and down buttons. emit noMoreParsingError(); QDoubleSpinBox::stepBy(steps); } void KisDoubleParseSpinBox::setValue(double value) { if(value == oldValue && hasFocus()){ //avoid to reset the button when it set the value of something that will recall this slot. return; } QDoubleSpinBox::setValue(value); if (!hasFocus()) { clearError(); } } void KisDoubleParseSpinBox::setErrorStyle() { if (!boolLastValid) { //setStyleSheet(_oldStyleSheet + "Background: red; color: white; padding-left: 18px;"); if (!isOldPaletteSaved) { oldPalette = palette(); } isOldPaletteSaved = true; QPalette nP = oldPalette; nP.setColor(QPalette::Background, Qt::red); nP.setColor(QPalette::Base, Qt::red); nP.setColor(QPalette::Text, Qt::white); setPalette(nP); if (!areOldMarginsSaved) { oldMargins = lineEdit()->textMargins(); } areOldMarginsSaved = true; if (width() - height() >= 3*height()) { //if we have twice as much place as needed by the warning icon then display it. QMargins newMargins = oldMargins; newMargins.setLeft( newMargins.left() + height() - 4 ); lineEdit()->setTextMargins(newMargins); int h = warningIcon->height(); int hp = height()-2; if (h != hp) { warningIcon->resize(hp, hp); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(hp-7, hp-7)); } } warningIcon->move(oldMargins.left()+4, 1); warningIcon->setVisible(true); } } } void KisDoubleParseSpinBox::clearErrorStyle() { if (boolLastValid) { warningIcon->setVisible(false); //setStyleSheet(QString()); setPalette(oldPalette); isOldPaletteSaved = false; lineEdit()->setTextMargins(oldMargins); areOldMarginsSaved = false; } } void KisDoubleParseSpinBox::clearError() { boolLastValid = true; emit noMoreParsingError(); oldValue = value(); clearErrorStyle(); } diff --git a/libs/widgetutils/kis_int_parse_spin_box.cpp b/libs/widgetutils/kis_int_parse_spin_box.cpp index 21c2707295..5ac4782f83 100644 --- a/libs/widgetutils/kis_int_parse_spin_box.cpp +++ b/libs/widgetutils/kis_int_parse_spin_box.cpp @@ -1,254 +1,249 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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_int_parse_spin_box.h" #include "kis_num_parser.h" #include #include #include #include #include KisIntParseSpinBox::KisIntParseSpinBox(QWidget *parent) : QSpinBox(parent), boolLastValid(true) { setAlignment(Qt::AlignRight); lastExprParsed = new QString("0"); connect(this, SIGNAL(noMoreParsingError()), this, SLOT(clearErrorStyle())); //hack to let the clearError be called, even if the value changed method is the one from QSpinBox. connect(this, SIGNAL(valueChanged(int)), this, SLOT(clearError())); connect(this, SIGNAL(errorWhileParsing(QString)), this, SLOT(setErrorStyle())); oldVal = value(); warningIcon = new QLabel(this); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16)); } else { warningIcon->setText("!"); } warningIcon->setStyleSheet("background:transparent;"); warningIcon->move(1, 1); warningIcon->setVisible(false); isOldPaletteSaved = false; areOldMarginsSaved = false; } KisIntParseSpinBox::~KisIntParseSpinBox() { //needed to avoid a segfault during destruction. delete lastExprParsed; } int KisIntParseSpinBox::valueFromText(const QString & text) const { *lastExprParsed = text; bool ok; int val; if ( (suffix().isEmpty() || !text.endsWith(suffix())) && (prefix().isEmpty() || !text.startsWith(prefix())) ) { val = KisNumericParser::parseIntegerMathExpr(text, &ok); } else { QString expr = text; if (text.endsWith(suffix())) { expr.remove(text.size()-suffix().size(), suffix().size()); } if(text.startsWith(prefix())){ expr.remove(0, prefix().size()); } *lastExprParsed = expr; val = KisNumericParser::parseIntegerMathExpr(expr, &ok); } if (text.trimmed().isEmpty()) { //an empty text is considered valid in this case. ok = true; } if (!ok) { if (boolLastValid == true) { oldVal = value(); } boolLastValid = false; //emit errorWhileParsing(text); //if uncommented become red every time the string is wrong. val = oldVal; } else { if (boolLastValid == false) { oldVal = val; } boolLastValid = true; //emit noMoreParsingError(); } return val; } QString KisIntParseSpinBox::textFromValue(int val) const { if (!boolLastValid) { emit errorWhileParsing(*lastExprParsed); return *lastExprParsed; } emit noMoreParsingError(); - int v = KisNumericParser::parseIntegerMathExpr(cleanText()); - if (hasFocus() && (v == value() || (v >= maximum() && value() == maximum()) || (v <= minimum() && value() == minimum())) ) { //solve a very annoying bug where the formula can collapse while editing. With this trick the formula is not lost until focus is lost. - return cleanText(); - } - return QSpinBox::textFromValue(val); } QValidator::State KisIntParseSpinBox::validate ( QString & input, int & pos ) const { Q_UNUSED(input); Q_UNUSED(pos); //this simple definition is sufficient for the moment //TODO: see if needed to get something more complex. return QValidator::Acceptable; } void KisIntParseSpinBox::stepBy(int steps) { boolLastValid = true; emit noMoreParsingError(); QSpinBox::stepBy(steps); } void KisIntParseSpinBox::setValue(int val) { if(val == oldVal && hasFocus()){ //avoid to reset the button when it set the value of something that will recall this slot. return; } if (!hasFocus()) { clearError(); } QSpinBox::setValue(val); } void KisIntParseSpinBox::setErrorStyle() { if (!boolLastValid) { //setStyleSheet(_oldStyleSheet + "Background: red; color: white; padding-left: 18px;"); if (!isOldPaletteSaved) { oldPalette = palette(); } isOldPaletteSaved = true; QPalette nP = oldPalette; nP.setColor(QPalette::Background, Qt::red); nP.setColor(QPalette::Base, Qt::red); nP.setColor(QPalette::Text, Qt::white); setPalette(nP); if (!areOldMarginsSaved) { oldMargins = lineEdit()->textMargins(); } areOldMarginsSaved = true; if (width() - height() >= 3*height()) { //if we have twice as much place as needed by the warning icon then display it. QMargins newMargins = oldMargins; newMargins.setLeft( newMargins.left() + height() - 4 ); lineEdit()->setTextMargins(newMargins); int h = warningIcon->height(); int hp = height()-2; if (h != hp) { warningIcon->resize(hp, hp); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(hp-7, hp-7)); } } warningIcon->move(oldMargins.left()+4, 1); warningIcon->setVisible(true); } } } void KisIntParseSpinBox::clearErrorStyle() { if (boolLastValid) { warningIcon->setVisible(false); //setStyleSheet(QString()); setPalette(oldPalette); isOldPaletteSaved = false; lineEdit()->setTextMargins(oldMargins); areOldMarginsSaved = false; } } void KisIntParseSpinBox::clearError() { boolLastValid = true; emit noMoreParsingError(); oldVal = value(); clearErrorStyle(); } diff --git a/plugins/dockers/animation/timeline_frames_index_converter.cpp b/plugins/dockers/animation/timeline_frames_index_converter.cpp index 17c964f8db..34fefc1513 100644 --- a/plugins/dockers/animation/timeline_frames_index_converter.cpp +++ b/plugins/dockers/animation/timeline_frames_index_converter.cpp @@ -1,139 +1,139 @@ /* * 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 "timeline_frames_index_converter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" TimelineFramesIndexConverter::TimelineFramesIndexConverter(KisDummiesFacadeBase *dummiesFacade) : m_dummiesFacade(dummiesFacade), m_activeDummy(0) { } TimelineFramesIndexConverter::~TimelineFramesIndexConverter() { } bool TimelineFramesIndexConverter::calcNodesInPath(KisNodeDummy *root, int &startCount, KisNodeDummy *endDummy) { if (isDummyVisible(root)) { if (endDummy && root == endDummy) { return true; } startCount++; } KisNodeDummy *dummy = root->lastChild(); while (dummy) { if (calcNodesInPath(dummy, startCount, endDummy)) { return true; } dummy = dummy->prevSibling(); } return false; } KisNodeDummy* TimelineFramesIndexConverter::findNodeFromRow(KisNodeDummy *root, int &startCount) { if (isDummyVisible(root)) { if (!startCount) { return root; } startCount--; } KisNodeDummy *dummy = root->lastChild(); while (dummy) { KisNodeDummy *found = findNodeFromRow(dummy, startCount); if (found) return found; dummy = dummy->prevSibling(); } return 0; } KisNodeDummy* TimelineFramesIndexConverter::dummyFromRow(int row) { KisNodeDummy *root = m_dummiesFacade->rootDummy(); if(!root) return 0; return findNodeFromRow(root, row); } int TimelineFramesIndexConverter::rowForDummy(KisNodeDummy *dummy) { if (!dummy) return -1; KisNodeDummy *root = m_dummiesFacade->rootDummy(); if(!root) return -1; int count = 0; return calcNodesInPath(root, count, dummy) ? count : -1; } int TimelineFramesIndexConverter::rowCount() { KisNodeDummy *root = m_dummiesFacade->rootDummy(); if(!root) return 0; int count = 0; calcNodesInPath(root, count, 0); return count; } KisNodeDummy* TimelineFramesIndexConverter::activeDummy() const { return m_activeDummy; } void TimelineFramesIndexConverter::updateActiveDummy(KisNodeDummy *dummy, bool *oldRemoved, bool *newAdded) { if (m_activeDummy == dummy) return; if (m_activeDummy && !m_activeDummy->node()->useInTimeline()) { *oldRemoved = true; } m_activeDummy = dummy; if (m_activeDummy && !m_activeDummy->node()->useInTimeline()) { *newAdded = true; } } void TimelineFramesIndexConverter::notifyDummyRemoved(KisNodeDummy *dummy) { if (m_activeDummy && m_activeDummy == dummy) { m_activeDummy = 0; } } bool TimelineFramesIndexConverter::isDummyVisible(KisNodeDummy *dummy) const { - return dummy->node()->useInTimeline() || dummy == m_activeDummy; + return (dummy->node()->useInTimeline() && dummy->parent()) || dummy == m_activeDummy; } diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index 2b1f545204..53abd44a6f 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,1021 +1,1023 @@ /* * 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 "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include "KisPart.h" #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_processing_applicator.h" #include #include "kis_node_uuid_info.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool frameHasContent(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return false; // first check if we are a key frame KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return false; return frame->hasContent(); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; nodeInterface->setNodeProperties(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { beginResetModel(); endResetModel(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::slotImageContentChanged() { if (m_d->activeLayerIndex < 0) return; KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); if (!dummy) return; slotDummyChanged(dummy); } void TimelineFramesModel::processUpdateQueue() { + if (!m_d->converter) return; + Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if (!dummy) { // It's perfectly normal that dummyForNode returns 0; that happens // when views get activated while Krita is closing down. return; } m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameHasContent: { return m_d->frameHasContent(index.row(), index.column()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case KoResourceModel::LargeThumbnailRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; QSize size = dummy->node()->extent().size(); size.scale(maxSize, maxSize, Qt::KeepAspectRatio); if (size.width() == 0 || size.height() == 0) { // No thumbnail can be shown if there isn't width or height... return QVariant(); } QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedMask()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy); } QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, TimelineFramesModel::MimeCopyPolicy copyPolicy) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = baseIndex.row(); const int baseColumn = baseIndex.column(); const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122(); stream << int(uuidDataRoot.size()); stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size()); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } stream << index.row() - baseRow << index.column() - baseColumn; const QByteArray uuidData = node->uuid().toRfc4122(); stream << int(uuidData.size()); stream.writeRawData(uuidData.data(), uuidData.size()); } stream << int(copyPolicy); data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); return dropMimeDataExtended(data, action, parent); } bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved) { bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int uuidLenRoot = 0; stream >> uuidLenRoot; QByteArray uuidDataRoot(uuidLenRoot, '\0'); stream.readRawData(uuidDataRoot.data(), uuidLenRoot); QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot); KisPart *partInstance = KisPart::instance(); QList> documents = partInstance->documents(); KisImageSP srcImage = 0; Q_FOREACH(KisDocument *doc, documents) { KisImageSP tmpSrcImage = doc->image(); if (tmpSrcImage->root()->uuid() == nodeUuidRoot) { srcImage = tmpSrcImage; break; } } if (!srcImage) { KisPart *kisPartInstance = KisPart::instance(); kisPartInstance->currentMainwindow()->viewManager()->showFloatingMessage( i18n("Dropped frames are not available in this Krita instance") , QIcon()); return false; } int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); KisAnimationUtils::FrameMovePairList frameMoves; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; const int srcRow = baseRow + relRow; const int srcColumn = baseColumn + relColumn; int uuidLen = 0; stream >> uuidLen; QByteArray uuidData(uuidLen, '\0'); stream.readRawData(uuidData.data(), uuidLen); QUuid nodeUuid = QUuid::fromRfc4122(uuidData); KisNodeSP srcNode; if (!nodeUuid.isNull()) { KisNodeUuidInfo nodeInfo(nodeUuid); srcNode = nodeInfo.findNode(srcImage->root()); } else { QModelIndex index = this->index(srcRow, srcColumn); srcNode = nodeAt(index); } KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; } const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0); if (!dstRowIndex.isValid()) continue; KisNodeSP dstNode = nodeAt(dstRowIndex); KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; } Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisAnimationUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn); KisAnimationUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x()); frameMoves << std::make_pair(srcItem, dstItem); } } MimeCopyPolicy copyPolicy = UndefinedPolicy; if (!stream.atEnd()) { int value = 0; stream >> value; copyPolicy = MimeCopyPolicy(value); } const bool copyFrames = copyPolicy == UndefinedPolicy ? action == Qt::CopyAction : copyPolicy == CopyFramesPolicy; if (dataMoved) { *dataMoved = !copyFrames; } KUndo2Command *cmd = 0; if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } return cmd; } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } bool TimelineFramesModel::insertFrames(int dstColumn, const QList &dstRows, int count, int timing) { if (dstRows.isEmpty() || count <= 0) return true; timing = qMax(timing, 1); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count)); { KisImageBarrierLockerWithFeedback locker(m_d->image); QModelIndexList indexes; Q_FOREACH (int row, dstRows) { for (int column = dstColumn; column < columnCount(); column++) { indexes << index(row, column); } } setLastVisibleFrame(columnCount() + (count * timing) - 1); createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); if (!dummy) continue; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) continue; for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) { KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand); } } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int count) { if (selectedIndexes.isEmpty() || count == 0) return true; QScopedPointer parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QSet uniqueKeyframesInSelection; int minSelectedTime = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; minSelectedTime = qMin(minSelectedTime, index.column()); KisKeyframeSP keyFrame = channel->activeKeyframeAt(index.column()); if (keyFrame) { uniqueKeyframesInSelection.insert(keyFrame); } } QList keyframesToMove; for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) { KisKeyframeSP keyframe = *it; KisKeyframeChannel *channel = keyframe->channel(); KisKeyframeSP nextKeyframe = channel->nextKeyframe(keyframe); if (nextKeyframe) { keyframesToMove << nextKeyframe; } } std::sort(keyframesToMove.begin(), keyframesToMove.end(), [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { return lhs->time() > rhs->time(); }); if (keyframesToMove.isEmpty()) return true; const int maxColumn = columnCount(); if (count > 0) { setLastVisibleFrame(columnCount() + count); } Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { int plannedFrameMove = count; if (count < 0) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0, false); KisKeyframeSP prevFrame = keyframe->channel()->previousKeyframe(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); plannedFrameMove = qMax(count, prevFrame->time() - keyframe->time() + 1); minSelectedTime = qMin(minSelectedTime, prevFrame->time()); } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(keyframe->channel()->node()); KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } const int row = m_d->converter->rowForDummy(dummy); KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; } QModelIndexList indexes; for (int column = keyframe->time(); column < maxColumn; column++) { indexes << index(row, column); } createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, true, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minSelectedTime; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand.data()); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } void TimelineFramesModel::setFullClipRangeStart(int column) { m_d->image->animationInterface()->setFullClipRangeStartTime(column); } void TimelineFramesModel::setFullClipRangeEnd(int column) { m_d->image->animationInterface()->setFullClipRangeEndTime(column); } diff --git a/plugins/dockers/historydocker/KisUndoModel.cpp b/plugins/dockers/historydocker/KisUndoModel.cpp index ae1ceb5a0d..f1326739a0 100644 --- a/plugins/dockers/historydocker/KisUndoModel.cpp +++ b/plugins/dockers/historydocker/KisUndoModel.cpp @@ -1,283 +1,283 @@ /* 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "KisUndoModel.h" #include KisUndoModel::KisUndoModel(QObject *parent) : QAbstractItemModel(parent) { m_blockOutgoingHistoryChange = false; m_stack = 0; m_canvas = 0; m_sel_model = new QItemSelectionModel(this, this); connect(m_sel_model, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(setStackCurrentIndex(QModelIndex))); m_empty_label = i18n(""); } QItemSelectionModel *KisUndoModel::selectionModel() const { return m_sel_model; } KUndo2QStack *KisUndoModel::stack() const { return m_stack; } void KisUndoModel::setStack(KUndo2QStack *stack) { if (m_stack == stack) return; if (m_stack != 0) { disconnect(m_stack, SIGNAL(canRedoChanged(bool)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*))); disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int))); } m_stack = stack; if (m_stack != 0) { connect(m_stack, SIGNAL(canRedoChanged(bool)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*))); connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int))); } stackChanged(); } void KisUndoModel::stackDestroyed(QObject *obj) { if (obj != m_stack) return; m_stack = 0; stackChanged(); } void KisUndoModel::stackChanged() { beginResetModel(); endResetModel(); m_blockOutgoingHistoryChange = true; m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect); m_blockOutgoingHistoryChange = false; } void KisUndoModel::setStackCurrentIndex(const QModelIndex &index) { if (m_blockOutgoingHistoryChange) return; if (m_stack == 0) return; if (index == selectedIndex()) return; if (index.column() != 0) return; m_stack->setIndex(index.row()); } QModelIndex KisUndoModel::selectedIndex() const { return m_stack == 0 ? QModelIndex() : createIndex(m_stack->index(), 0); } QModelIndex KisUndoModel::index(int row, int column, const QModelIndex &parent) const { if (m_stack == 0) return QModelIndex(); if (parent.isValid()) return QModelIndex(); if (column != 0) return QModelIndex(); if (row < 0 || row > m_stack->count()) return QModelIndex(); return createIndex(row, column); } QModelIndex KisUndoModel::parent(const QModelIndex&) const { return QModelIndex(); } int KisUndoModel::rowCount(const QModelIndex &parent) const { if (m_stack == 0) return 0; if (parent.isValid()) return 0; return m_stack->count() + 1; } int KisUndoModel::columnCount(const QModelIndex&) const { return 1; } QVariant KisUndoModel::data(const QModelIndex &index, int role) const { if (m_stack == 0){ return QVariant(); } if (index.column() != 0){ return QVariant(); } if (index.row() < 0 || index.row() > m_stack->count()){ return QVariant(); } if (role == Qt::DisplayRole) { if (index.row() == 0){ return m_empty_label; } KUndo2Command* currentCommand = const_cast(m_stack->command(index.row() - 1)); return currentCommand->isMerged()?m_stack->text(index.row() - 1)+"(Merged)":m_stack->text(index.row() - 1); } else if (role == Qt::DecorationRole) { if (index.row() > 0) { const KUndo2Command* currentCommand = m_stack->command(index.row() - 1); if (m_imageMap.contains(currentCommand)) { return m_imageMap[currentCommand]; } } } return QVariant(); } QString KisUndoModel::emptyLabel() const { return m_empty_label; } void KisUndoModel::setEmptyLabel(const QString &label) { m_empty_label = label; stackChanged(); } void KisUndoModel::setCleanIcon(const QIcon &icon) { m_clean_icon = icon; stackChanged(); } QIcon KisUndoModel::cleanIcon() const { return m_clean_icon; } void KisUndoModel::setCanvas(KisCanvas2 *canvas) { m_canvas = canvas; } void KisUndoModel::addImage(int idx) { if (m_stack == 0 || m_stack->count() == 0) { return; } const KUndo2Command* currentCommand = m_stack->command(idx-1); if (m_stack->count() == idx && !m_imageMap.contains(currentCommand)) { - KisImageWSP historyImage = m_canvas->viewManager()->image(); + KisImageWSP historyImage = m_canvas->image(); KisPaintDeviceSP paintDevice = historyImage->projection(); QImage image = paintDevice->createThumbnail(32, 32, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); m_imageMap[currentCommand] = image; } QList list; for (int i = 0; i < m_stack->count(); ++i) { list << m_stack->command(i); } for (QMap:: iterator it = m_imageMap.begin(); it != m_imageMap.end();) { if (!list.contains(it.key())) { it = m_imageMap.erase(it); } else { ++it; } } } bool KisUndoModel::checkMergedCommand(int index) { Q_UNUSED(index) return false; } diff --git a/plugins/filters/blur/kis_motion_blur_filter.cpp b/plugins/filters/blur/kis_motion_blur_filter.cpp index ac8a1ed952..60ee8ef7f9 100644 --- a/plugins/filters/blur/kis_motion_blur_filter.cpp +++ b/plugins/filters/blur/kis_motion_blur_filter.cpp @@ -1,167 +1,174 @@ /* * This file is part of Krita * * Copyright (c) 2010 Edward Apap * * 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_motion_blur_filter.h" #include "kis_wdg_motion_blur.h" #include #include #include #include "ui_wdg_motion_blur.h" #include #include #include #include #include #include "kis_lod_transform.h" #include #include KisMotionBlurFilter::KisMotionBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Motion Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisMotionBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgMotionBlur(parent); } KisFilterConfigurationSP KisMotionBlurFilter::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); config->setProperty("blurAngle", 0); config->setProperty("blurLength", 5); return config; } void KisMotionBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = rect.topLeft(); - Q_ASSERT(device != 0); + Q_ASSERT(device); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); QVariant value; - config->getProperty("blurAngle", value); - uint blurAngle = value.toUInt(); + uint blurAngle = 0; + if (config->getProperty("blurAngle", value)) { + blurAngle = value.toUInt(); + } KisLodTransformScalar t(device); - config->getProperty("blurLength", value); - uint blurLength = t.scale(value.toUInt()); + uint blurLength = 0; + if (config->getProperty("blurLength", value)) { + blurLength = t.scale(value.toUInt()); + } - if (blurLength == 0) + if (blurLength == 0) { return; + } QBitArray channelFlags; + if (config) { channelFlags = config->channelFlags(); } + if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } // convert angle to radians qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; // construct image qreal halfWidth = blurLength / 2.0 * cos(angleRadians); qreal halfHeight = blurLength / 2.0 * sin(angleRadians); int kernelWidth = ceil(fabs(halfWidth)) * 2; int kernelHeight = ceil(fabs(halfHeight)) * 2; // check for zero dimensions (vertical/horizontal motion vectors) kernelWidth = (kernelWidth == 0) ? 1 : kernelWidth; kernelHeight = (kernelHeight == 0) ? 1 : kernelHeight; QImage kernelRepresentation(kernelWidth, kernelHeight, QImage::Format_RGB32); kernelRepresentation.fill(0); QPainter imagePainter(&kernelRepresentation); imagePainter.setRenderHint(QPainter::Antialiasing); imagePainter.setPen(QPen(QColor::fromRgb(255, 255, 255), 1.0)); imagePainter.drawLine(QPointF(kernelWidth / 2 - halfWidth, kernelHeight / 2 + halfHeight), QPointF(kernelWidth / 2 + halfWidth, kernelHeight / 2 - halfHeight)); // construct kernel from image Eigen::Matrix motionBlurKernel(kernelHeight, kernelWidth); for (int j = 0; j < kernelHeight; ++j) { for (int i = 0; i < kernelWidth; ++i) { motionBlurKernel(j, i) = qRed(kernelRepresentation.pixel(i, j)); } } // apply convolution KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(motionBlurKernel, 0, motionBlurKernel.sum()); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } QRect KisMotionBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; uint blurAngle = _config->getProperty("blurAngle", value) ? value.toUInt() : 0; uint blurLength = t.scale(_config->getProperty("blurLength", value) ? value.toUInt() : 5); qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; const int halfWidth = ceil(fabs(blurLength / 2.0 * cos(angleRadians))); const int halfHeight = ceil(fabs(blurLength / 2.0 * cos(angleRadians))); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisMotionBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; uint blurAngle = _config->getProperty("blurAngle", value) ? value.toUInt() : 0; uint blurLength = t.scale(_config->getProperty("blurLength", value) ? value.toUInt() : 5); qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; const int halfWidth = ceil(fabs(blurLength * cos(angleRadians))); const int halfHeight = ceil(fabs(blurLength * cos(angleRadians))); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } diff --git a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp index b2dce21a6b..0d513e3cb6 100644 --- a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp +++ b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp @@ -1,170 +1,174 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * 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_convert_height_to_normal_map_filter.h" #include "kis_wdg_convert_height_to_normal_map.h" #include #include #include #include #include #include "kis_lod_transform.h" #include K_PLUGIN_FACTORY_WITH_JSON(KritaConvertHeightToNormalMapFilterFactory, "kritaconvertheighttonormalmap.json", registerPlugin();) KritaConvertHeightToNormalMapFilter::KritaConvertHeightToNormalMapFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisConvertHeightToNormalMapFilter())); } KritaConvertHeightToNormalMapFilter::~KritaConvertHeightToNormalMapFilter() { } KisConvertHeightToNormalMapFilter::KisConvertHeightToNormalMapFilter(): KisFilter(id(), FiltersCategoryEdgeDetectionId, i18n("&Height to Normal Map...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); } void KisConvertHeightToNormalMapFilter::processImpl(KisPaintDeviceSP device, const QRect &rect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { - Q_ASSERT(device != 0); + Q_ASSERT(device); KisFilterConfigurationSP configuration = config ? config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; - configuration->getProperty("horizRadius", value); - float horizontalRadius = t.scale(value.toFloat()); - configuration->getProperty("vertRadius", value); - float verticalRadius = t.scale(value.toFloat()); + float horizontalRadius = 1.0; + if (configuration->getProperty("horizRadius", value)) { + horizontalRadius = t.scale(value.toFloat()); + } + float verticalRadius = 1.0; + if (configuration->getProperty("vertRadius", value)) { + verticalRadius = t.scale(value.toFloat()); + } QBitArray channelFlags; if (configuration) { channelFlags = configuration->channelFlags(); } if (channelFlags.isEmpty() || !configuration) { channelFlags = device->colorSpace()->channelFlags(); } KisEdgeDetectionKernel::FilterType type = KisEdgeDetectionKernel::SobelVector; if (configuration->getString("type") == "prewitt") { type = KisEdgeDetectionKernel::Prewit; } else if (configuration->getString("type") == "simple") { type = KisEdgeDetectionKernel::Simple; } int channelToConvert = configuration->getInt("channelToConvert", 0); QVector channelOrder(3); QVector channelFlip(3); channelFlip.fill(false); int i = config->getInt("redSwizzle", 0); if (i%2==1 || i==2) { channelFlip[0] = true; } if (i==3) { channelFlip[0] = false; } channelOrder[device->colorSpace()->channels().at(0)->displayPosition()] = qMax(i/2,0); i = config->getInt("greenSwizzle", 2); if (i%2==1 || i==2) { channelFlip[1] = true; } if (i==3) { channelFlip[1] = false; } channelOrder[device->colorSpace()->channels().at(1)->displayPosition()] = qMax(i/2,0); i = config->getInt("blueSwizzle", 4); if (i%2==1 || i==2) { channelFlip[2] = true; } if (i==3) { channelFlip[2] = false; } channelOrder[device->colorSpace()->channels().at(2)->displayPosition()] = qMax(i/2,0); KisEdgeDetectionKernel::convertToNormalMap(device, rect, horizontalRadius, verticalRadius, type, channelToConvert, channelOrder, channelFlip, channelFlags, progressUpdater); } KisFilterConfigurationSP KisConvertHeightToNormalMapFilter::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); config->setProperty("horizRadius", 1); config->setProperty("vertRadius", 1); config->setProperty("type", "sobol"); config->setProperty("channelToConvert", 0); config->setProperty("lockAspect", true); config->setProperty("redSwizzle", KisWdgConvertHeightToNormalMap::xPlus); config->setProperty("greenSwizzle", KisWdgConvertHeightToNormalMap::yPlus); config->setProperty("blueSwizzle", KisWdgConvertHeightToNormalMap::zPlus); return config; } KisConfigWidget *KisConvertHeightToNormalMapFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { return new KisWdgConvertHeightToNormalMap(parent, dev->colorSpace()); } QRect KisConvertHeightToNormalMapFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; /** * NOTE: integer division by two is done on purpose, * because the kernel size is always odd */ const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisConvertHeightToNormalMapFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfWidth, -halfHeight, halfWidth, halfHeight); } #include "kis_convert_height_to_normal_map_filter.moc" diff --git a/plugins/filters/phongbumpmap/phong_pixel_processor.cpp b/plugins/filters/phongbumpmap/phong_pixel_processor.cpp index a906860bc7..9484331745 100644 --- a/plugins/filters/phongbumpmap/phong_pixel_processor.cpp +++ b/plugins/filters/phongbumpmap/phong_pixel_processor.cpp @@ -1,202 +1,203 @@ /* * Copyright (c) 2010-2012 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "phong_pixel_processor.h" #include #include #include PhongPixelProcessor::PhongPixelProcessor(quint32 pixelArea, const KisPropertiesConfigurationSP config) { m_pixelArea = pixelArea; initialize(config); } void PhongPixelProcessor::initialize(const KisPropertiesConfigurationSP config) { // Basic, fundamental normal_vector = QVector3D(0, 0, 1); vision_vector = QVector3D(0, 0, 1); x_vector = QVector3D(1, 0, 0); y_vector = QVector3D(0, 1, 0); // Mutable light_vector = QVector3D(0, 0, 0); reflection_vector = QVector3D(0, 0, 0); //setLightVector(QVector3D(-8, 8, 2)); Illuminant light[PHONG_TOTAL_ILLUMINANTS]; QVariant guiLight[PHONG_TOTAL_ILLUMINANTS]; qint32 azimuth; qint32 inclination; for (int i = 0; i < PHONG_TOTAL_ILLUMINANTS; i++) { if (config->getBool(PHONG_ILLUMINANT_IS_ENABLED[i])) { - config->getProperty(PHONG_ILLUMINANT_COLOR[i], guiLight[i]); - light[i].RGBvalue << guiLight[i].value().redF(); - light[i].RGBvalue << guiLight[i].value().greenF(); - light[i].RGBvalue << guiLight[i].value().blueF(); - - azimuth = config->getInt(PHONG_ILLUMINANT_AZIMUTH[i]) - 90; - inclination = config->getInt(PHONG_ILLUMINANT_INCLINATION[i]); - - qreal m; //2D vector magnitude - light[i].lightVector.setZ( sin( inclination * M_PI / 180 ) ); - m = cos( inclination * M_PI / 180); - - light[i].lightVector.setX( cos( azimuth * M_PI / 180 ) * m ); - light[i].lightVector.setY( sin( azimuth * M_PI / 180 ) * m ); - //Pay close attention to this, indexes will move in this line - lightSources.append(light[i]); + if (config->getProperty(PHONG_ILLUMINANT_COLOR[i], guiLight[i])) { + light[i].RGBvalue << guiLight[i].value().redF(); + light[i].RGBvalue << guiLight[i].value().greenF(); + light[i].RGBvalue << guiLight[i].value().blueF(); + + azimuth = config->getInt(PHONG_ILLUMINANT_AZIMUTH[i]) - 90; + inclination = config->getInt(PHONG_ILLUMINANT_INCLINATION[i]); + + qreal m; //2D vector magnitude + light[i].lightVector.setZ( sin( inclination * M_PI / 180 ) ); + m = cos( inclination * M_PI / 180); + + light[i].lightVector.setX( cos( azimuth * M_PI / 180 ) * m ); + light[i].lightVector.setY( sin( azimuth * M_PI / 180 ) * m ); + //Pay close attention to this, indexes will move in this line + lightSources.append(light[i]); + } } } size = lightSources.size(); //Code that exists only to swiftly switch to the other algorithm (reallyFastIlluminatePixel) to test if (size > 0) { fastLight = light[0]; fastLight2 = light[0]; } //Ka, Kd and Ks must be between 0 and 1 or grave errors will happen Ka = config->getDouble(PHONG_AMBIENT_REFLECTIVITY); Kd = config->getDouble(PHONG_DIFFUSE_REFLECTIVITY); Ks = config->getDouble(PHONG_SPECULAR_REFLECTIVITY); shiny_exp = config->getInt(PHONG_SHINYNESS_EXPONENT); Ia = Id = Is = 0; diffuseLightIsEnabled = config->getBool(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED); specularLightIsEnabled = config->getBool(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED); realheightmap = QVector(m_pixelArea, 0); } PhongPixelProcessor::~PhongPixelProcessor() { } void PhongPixelProcessor::setLightVector(QVector3D lightVector) { lightVector.normalize(); light_vector = lightVector; } QVector PhongPixelProcessor::IlluminatePixelFromHeightmap(quint32 posup, quint32 posdown, quint32 posleft, quint32 posright) { QVector finalPixel(4, 0xFFFF); if (lightSources.size() == 0) return finalPixel; // Algorithm begins, Phong Illumination Model normal_vector.setX(- realheightmap[posright] + realheightmap[posleft]); normal_vector.setY(- realheightmap[posup] + realheightmap[posdown]); normal_vector.setZ(8); normal_vector.normalize(); // PREPARE ALGORITHM HERE finalPixel = IlluminatePixel(); return finalPixel; - } - +} + QVector PhongPixelProcessor::IlluminatePixel() { qreal temp; quint8 channel = 0; const quint8 totalChannels = 3; // The 4th is alpha and we'll fill it with a nice 0xFFFF qreal computation[] = {0, 0, 0}; QVector finalPixel(4, 0xFFFF); if (lightSources.size() == 0) return finalPixel; // PREPARE ALGORITHM HERE for (int i = 0; i < size; i++) { light_vector = lightSources.at(i).lightVector; for (channel = 0; channel < totalChannels; channel++) { Ia = lightSources.at(i).RGBvalue.at(channel) * Ka; computation[channel] += Ia; } if (diffuseLightIsEnabled) { temp = Kd * QVector3D::dotProduct(normal_vector, light_vector); for (channel = 0; channel < totalChannels; channel++) { Id = lightSources.at(i).RGBvalue.at(channel) * temp; if (Id < 0) Id = 0; if (Id > 1) Id = 1; computation[channel] += Id; } } if (specularLightIsEnabled) { reflection_vector = (2 * pow(QVector3D::dotProduct(normal_vector, light_vector), shiny_exp)) * normal_vector - light_vector; temp = Ks * QVector3D::dotProduct(vision_vector, reflection_vector); for (channel = 0; channel < totalChannels; channel++) { Is = lightSources.at(i).RGBvalue.at(channel) * temp; if (Is < 0) Is = 0; if (Is > 1) Is = 1; computation[channel] += Is; } } } for (channel = 0; channel < totalChannels; channel++) { if (computation[channel] > 1) computation[channel] = 1; if (computation[channel] < 0) computation[channel] = 0; } //RGBA actually uses the BGRA order of channels, hence the disorder finalPixel[2] = quint16(computation[0] * 0xFFFF); finalPixel[1] = quint16(computation[1] * 0xFFFF); finalPixel[0] = quint16(computation[2] * 0xFFFF); return finalPixel; - } - +} + QVector PhongPixelProcessor::IlluminatePixelFromNormalmap(qreal r, qreal g, qreal b) - { +{ QVector finalPixel(4, 0xFFFF); if (lightSources.size() == 0) return finalPixel; // if () // Algorithm begins, Phong Illumination Model normal_vector.setX(r*2-1.0); normal_vector.setY(-(g*2-1.0)); normal_vector.setZ(b*2-1.0); //normal_vector.normalize(); // PREPARE ALGORITHM HERE finalPixel = IlluminatePixel(); return finalPixel; - } +} diff --git a/plugins/flake/artistictextshape/MoveStartOffsetStrategy.h b/plugins/flake/artistictextshape/MoveStartOffsetStrategy.h index 7316b07d05..762ce898c0 100644 --- a/plugins/flake/artistictextshape/MoveStartOffsetStrategy.h +++ b/plugins/flake/artistictextshape/MoveStartOffsetStrategy.h @@ -1,51 +1,51 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * 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 MOVESTARTOFFSETSTRATEGY_H #define MOVESTARTOFFSETSTRATEGY_H #include #include class KoPathShape; class ArtisticTextShape; class KoToolBase; /// A strategy to change the offset of a text when put on a path class MoveStartOffsetStrategy : public KoInteractionStrategy { public: MoveStartOffsetStrategy(KoToolBase *tool, ArtisticTextShape *text); ~MoveStartOffsetStrategy() override; // reimplemented from KoInteractionStrategy void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) override; // reimplemented from KoInteractionStrategy KUndo2Command *createCommand() override; // reimplemented from KoInteractionStrategy void finishInteraction(Qt::KeyboardModifiers modifiers) override; private: - ArtisticTextShape *m_text; ///< the text shape we are working on - KoPathShape *m_baselineShape; ///< path shape the text is put on - qreal m_oldStartOffset; ///< the initial start offset + ArtisticTextShape *m_text {0}; ///< the text shape we are working on + KoPathShape *m_baselineShape {0}; ///< path shape the text is put on + qreal m_oldStartOffset {0}; ///< the initial start offset QList m_segmentLengths; ///< cached lengths of baseline path segments - qreal m_totalLength; ///< total length of baseline path + qreal m_totalLength {0.0}; ///< total length of baseline path }; #endif // MOVESTARTOFFSETSTRATEGY_H diff --git a/plugins/flake/textshape/dialogs/BibliographyTemplate.cpp b/plugins/flake/textshape/dialogs/BibliographyTemplate.cpp index c75b5f1c37..74477790df 100644 --- a/plugins/flake/textshape/dialogs/BibliographyTemplate.cpp +++ b/plugins/flake/textshape/dialogs/BibliographyTemplate.cpp @@ -1,82 +1,55 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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 "BibliographyTemplate.h" -#include #include #include #include #include #include BibliographyTemplate::BibliographyTemplate(KoStyleManager *manager) : m_manager(manager) { Q_ASSERT(manager); } QList BibliographyTemplate::templates() { //if you are adding your own custom styles specifically for bibliography, add it as an unused style in KoStyleManager // when the bibliography is used the style will be automatically move to the usedStyle section QList predefinedTemplates; - KoBibliographyInfo *firstTemplate = new KoBibliographyInfo(); - firstTemplate->m_indexTitleTemplate.text = i18n("Bibliography"); - - firstTemplate->m_indexTitleTemplate.styleId = m_manager->defaultBibliographyTitleStyle()->styleId(); - firstTemplate->m_indexTitleTemplate.styleName = m_manager->defaultBibliographyTitleStyle()->name(); - - Q_FOREACH (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { - firstTemplate->m_entryTemplate[bibType].styleId = m_manager->defaultBibliographyEntryStyle(bibType)->styleId(); - firstTemplate->m_entryTemplate[bibType].styleName = m_manager->defaultBibliographyEntryStyle(bibType)->name(); - } - firstTemplate->m_entryTemplate = BibliographyGenerator::defaultBibliographyEntryTemplates(); - - KoBibliographyInfo *secondTemplate = new KoBibliographyInfo(); - secondTemplate->m_indexTitleTemplate.text = i18n("References"); - - secondTemplate->m_indexTitleTemplate.styleId = m_manager->defaultBibliographyTitleStyle()->styleId(); - secondTemplate->m_indexTitleTemplate.styleName = m_manager->defaultBibliographyTitleStyle()->name(); - - Q_FOREACH (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { - secondTemplate->m_entryTemplate[bibType].styleId = m_manager->defaultBibliographyEntryStyle(bibType)->styleId(); - secondTemplate->m_entryTemplate[bibType].styleName = m_manager->defaultBibliographyEntryStyle(bibType)->name(); - } - secondTemplate->m_entryTemplate = BibliographyGenerator::defaultBibliographyEntryTemplates(); - - predefinedTemplates.append(firstTemplate); - predefinedTemplates.append(secondTemplate); return predefinedTemplates; } void BibliographyTemplate::moveTemplateToUsed(KoBibliographyInfo *info) { if (m_manager->unusedStyle(info->m_indexTitleTemplate.styleId)) { m_manager->moveToUsedStyles(info->m_indexTitleTemplate.styleId); } Q_FOREACH (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { if (m_manager->unusedStyle(info->m_entryTemplate[bibType].styleId)) { m_manager->moveToUsedStyles(info->m_entryTemplate[bibType].styleId); } } } diff --git a/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.cpp b/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.cpp index 7c39f6e3d4..0e28e48921 100644 --- a/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.cpp +++ b/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.cpp @@ -1,259 +1,264 @@ /* This file is part of the KDE project * Copyright (C) 2011 Gopalakrishna Bhat A * * 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 "TableOfContentsStyleModel.h" #include "KoStyleManager.h" #include #include "KoParagraphStyle.h" #include "ToCBibGeneratorInfo.h" #include "KoTableOfContentsGeneratorInfo.h" #include "kis_assert.h" #include #include TableOfContentsStyleModel::TableOfContentsStyleModel(const KoStyleManager *manager, KoTableOfContentsGeneratorInfo *info) : QAbstractTableModel() , m_styleManager(manager) , m_styleThumbnailer(new KoStyleThumbnailer()) , m_tocInfo(info) { Q_ASSERT(manager); Q_ASSERT(info); m_styleThumbnailer->setThumbnailSize(QSize(250, 48)); Q_FOREACH (const KoParagraphStyle *style, m_styleManager->paragraphStyles()) { m_styleList.append(style->styleId()); m_outlineLevel.append(getOutlineLevel(style->styleId())); } } +TableOfContentsStyleModel::~TableOfContentsStyleModel() +{ + delete m_styleThumbnailer; +} + QModelIndex TableOfContentsStyleModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column > 1) { return QModelIndex(); } if (!parent.isValid()) { if (row >= m_styleList.count()) { return QModelIndex(); } QPair *modelValue = new QPair(m_styleList[row], m_outlineLevel[row]); return createIndex(row, column, modelValue); } return QModelIndex(); } int TableOfContentsStyleModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_styleList.count(); } return 0; } int TableOfContentsStyleModel::columnCount(const QModelIndex &parent) const { if (!parent.isValid()) { return 2; } return 0; } QVariant TableOfContentsStyleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } int id = static_cast< QPair *>(index.internalPointer())->first; if (index.column() == 0) { switch (role) { case Qt::DisplayRole: { return QVariant(); } case Qt::DecorationRole: { if (!m_styleThumbnailer) { return QPixmap(); } KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(id); if (paragStyle) { return m_styleThumbnailer->thumbnail(paragStyle); } break; } default: break; } } else { KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(id); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(paragStyle, QVariant()); switch (role) { case Qt::DisplayRole: { if (QVariant(static_cast< QPair *>(index.internalPointer())->second).value() == 0) { return QVariant(i18n("Disabled")); } else { return QVariant(static_cast< QPair *>(index.internalPointer())->second); } } case Qt::EditRole: { return QVariant(static_cast< QPair *>(index.internalPointer())->second); } default: break; } } return QVariant(); } Qt::ItemFlags TableOfContentsStyleModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return 0; } if (index.column() == 0) { return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); } if (index.column() == 1) { return (Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable); } return 0; } bool TableOfContentsStyleModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } static_cast< QPair *>(index.internalPointer())->second = value.toInt(); QAbstractTableModel::setData(index, value, role); m_outlineLevel[index.row()] = value.toInt(); return true; } void TableOfContentsStyleModel::saveData() { int row = 0; Q_FOREACH (const int styleId, m_styleList) { KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(styleId); if (paragStyle) { setOutlineLevel(styleId, m_outlineLevel[row]); } row++; } } int TableOfContentsStyleModel::getOutlineLevel(int styleId) { foreach (const IndexSourceStyles &indexSourceStyles, m_tocInfo->m_indexSourceStyles) { foreach (const IndexSourceStyle &indexStyle, indexSourceStyles.styles) { if (m_styleManager->paragraphStyle(indexStyle.styleId) && styleId == indexStyle.styleId) { return indexSourceStyles.outlineLevel; } } } return 0; } void TableOfContentsStyleModel::setOutlineLevel(int styleId, int outLineLevel) { //ignore changes to paragraph styles with KoParagraphStyle::OutlineLevel property set. //i.e. those considered by KoTableOfContentsGeneratorInfo::m_useOutlineLevel==true if (m_styleManager->paragraphStyle(styleId)->hasProperty(KoParagraphStyle::OutlineLevel)) { return; } //check if the outlineLevel has changed if (getOutlineLevel(styleId) == outLineLevel) { return; } //now insert the style at the correct place( remove from the old place first and then insert at the new level) IndexSourceStyle indexStyleMoved; bool styleFound = false; int sourceStyleIndex = 0; foreach (const IndexSourceStyles &indexSourceStyles, m_tocInfo->m_indexSourceStyles) { int index = 0; foreach (const IndexSourceStyle &indexStyle, indexSourceStyles.styles) { if (styleId == indexStyle.styleId) { styleFound = true; indexStyleMoved = m_tocInfo->m_indexSourceStyles[sourceStyleIndex].styles.takeAt(index); break; } index++; if (styleFound == true) { break; } } sourceStyleIndex++; } //this style is not in the IndexSourceStyles list so fill it if (!styleFound) { indexStyleMoved.styleId = styleId; indexStyleMoved.styleName = m_styleManager->paragraphStyle(styleId)->name(); } //check if IndexSourceStyles are there for this outlineLevel, if not create it bool sourceStylePresent = false; foreach (const IndexSourceStyles &indexSourceStyles, m_tocInfo->m_indexSourceStyles) { if (outLineLevel == indexSourceStyles.outlineLevel) { sourceStylePresent = true; break; } } if (!sourceStylePresent) { IndexSourceStyles indexStyles; indexStyles.outlineLevel = outLineLevel; m_tocInfo->m_indexSourceStyles.append(indexStyles); } sourceStyleIndex = 0; foreach (const IndexSourceStyles &indexSourceStyles, m_tocInfo->m_indexSourceStyles) { if (outLineLevel == indexSourceStyles.outlineLevel) { m_tocInfo->m_indexSourceStyles[sourceStyleIndex].styles.append(indexStyleMoved); break; } sourceStyleIndex++; } } QVariant TableOfContentsStyleModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Styles"); } else if (section == 1) { return i18n("Level"); } else { return QAbstractTableModel::headerData(section, orientation, role); } } else { return QAbstractTableModel::headerData(section, orientation, role); } } diff --git a/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.h b/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.h index 3e5c869376..9bcd8e2f3c 100644 --- a/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.h +++ b/plugins/flake/textshape/dialogs/TableOfContentsStyleModel.h @@ -1,58 +1,58 @@ /* This file is part of the KDE project * Copyright (C) 2011 Gopalakrishna Bhat A * * 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 TABLEOFCONTENTSSTYLEMODEL_H #define TABLEOFCONTENTSSTYLEMODEL_H #include class KoStyleManager; class KoStyleThumbnailer; class KoTableOfContentsGeneratorInfo; class TableOfContentsStyleModel : public QAbstractTableModel { public: TableOfContentsStyleModel(const KoStyleManager *manager, KoTableOfContentsGeneratorInfo *info); - + ~TableOfContentsStyleModel() override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void saveData(); protected: QList m_styleList; // list of style IDs QList m_outlineLevel; private: const KoStyleManager *m_styleManager; KoStyleThumbnailer *m_styleThumbnailer; KoTableOfContentsGeneratorInfo *m_tocInfo; int getOutlineLevel(int styleId); void setOutlineLevel(int styleId, int outLineLevel); }; #endif // TABLEOFCONTENTSSTYLEMODEL_H diff --git a/plugins/flake/textshape/kotext/BibliographyGenerator.cpp b/plugins/flake/textshape/kotext/BibliographyGenerator.cpp deleted file mode 100644 index 4fd10541b1..0000000000 --- a/plugins/flake/textshape/kotext/BibliographyGenerator.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2011 Smit Patel - * - * 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 "BibliographyGenerator.h" -#include "KoInlineCite.h" - -#include -#include -#include -#include -#include -#include - -#include - -static QList sortKeys; - -BibliographyGenerator::BibliographyGenerator(QTextDocument *bibDocument, const QTextBlock &block, KoBibliographyInfo *bibInfo) - : QObject(bibDocument) - , m_bibDocument(bibDocument) - , m_bibInfo(bibInfo) - , m_block(block) -{ - Q_ASSERT(bibDocument); - Q_ASSERT(bibInfo); - - m_bibInfo->setGenerator(this); - - bibDocument->setUndoRedoEnabled(false); - generate(); -} - -BibliographyGenerator::~BibliographyGenerator() -{ - delete m_bibInfo; -} - -static bool compare_on(int keyIndex, KoInlineCite *c1, KoInlineCite *c2) -{ - if ( keyIndex == sortKeys.size() ) return false; - else if (sortKeys[keyIndex].second == Qt::AscendingOrder) { - if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return true; - else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return false; - } else if (sortKeys[keyIndex].second == Qt::DescendingOrder) { - if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return false; - else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return true; - } else return compare_on( keyIndex + 1, c1, c2 ); - - return false; -} - -static bool lessThan(KoInlineCite *c1, KoInlineCite *c2) -{ - return compare_on(0, c1, c2); -} - -static QList sort(QList cites, QList keys) -{ - sortKeys = keys; - sortKeys << QPair("identifier", Qt::AscendingOrder); - std::sort(cites.begin(), cites.end(), lessThan); - return cites; -} - -void BibliographyGenerator::generate() -{ - if (!m_bibInfo) - return; - - QTextCursor cursor = m_bibDocument->rootFrame()->lastCursorPosition(); - cursor.setPosition(m_bibDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor); - cursor.beginEditBlock(); - - KoTextDocument koDocument(m_block.document()); - KoStyleManager *styleManager = koDocument.styleManager(); - - if (!m_bibInfo->m_indexTitleTemplate.text.isNull()) { - KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_bibInfo->m_indexTitleTemplate.styleId); - if (!titleStyle) { - titleStyle = styleManager->defaultBibliographyTitleStyle(); - m_bibInfo->m_indexTitleTemplate.styleName = titleStyle->name(); - } - - QTextBlock titleTextBlock = cursor.block(); - titleStyle->applyStyle(titleTextBlock); - - cursor.insertText(m_bibInfo->m_indexTitleTemplate.text); - cursor.insertBlock(); - } - - QTextCharFormat savedCharFormat = cursor.charFormat(); - - QList citeList; - if (styleManager->bibliographyConfiguration()->sortByPosition()) { - citeList = koDocument.inlineTextObjectManager()-> - citationsSortedByPosition(false, m_block.document()->firstBlock()); - } else { - citeList = sort(koDocument.inlineTextObjectManager()-> - citationsSortedByPosition(false, m_block.document()->firstBlock()), - koDocument.styleManager()->bibliographyConfiguration()->sortKeys()); - } - - foreach (KoInlineCite *cite, citeList) - { - KoParagraphStyle *bibTemplateStyle = 0; - BibliographyEntryTemplate bibEntryTemplate; - if (m_bibInfo->m_entryTemplate.contains(cite->bibliographyType())) { - - bibEntryTemplate = m_bibInfo->m_entryTemplate[cite->bibliographyType()]; - - bibTemplateStyle = styleManager->paragraphStyle(bibEntryTemplate.styleId); - if (bibTemplateStyle == 0) { - bibTemplateStyle = styleManager->defaultBibliographyEntryStyle(bibEntryTemplate.bibliographyType); - bibEntryTemplate.styleName = bibTemplateStyle->name(); - } - } else { - continue; - } - - cursor.insertBlock(QTextBlockFormat(),QTextCharFormat()); - - QTextBlock bibEntryTextBlock = cursor.block(); - bibTemplateStyle->applyStyle(bibEntryTextBlock); - bool spanEnabled = false; //true if data field is not empty - - foreach (IndexEntry * entry, bibEntryTemplate.indexEntries) { - switch(entry->name) { - case IndexEntry::BIBLIOGRAPHY: { - IndexEntryBibliography *indexEntry = static_cast(entry); - cursor.insertText(QString(((spanEnabled)?" ":"")).append(cite->dataField(indexEntry->dataField))); - spanEnabled = !cite->dataField(indexEntry->dataField).isEmpty(); - break; - } - case IndexEntry::SPAN: { - if(spanEnabled) { - IndexEntrySpan *span = static_cast(entry); - cursor.insertText(span->text); - } - break; - } - case IndexEntry::TAB_STOP: { - IndexEntryTabStop *tabEntry = static_cast(entry); - - cursor.insertText("\t"); - - QTextBlockFormat blockFormat = cursor.blockFormat(); - QList tabList; - if (tabEntry->m_position == "MAX") { - tabEntry->tab.position = m_maxTabPosition; - } else { - tabEntry->tab.position = tabEntry->m_position.toDouble(); - } - tabList.append(QVariant::fromValue(tabEntry->tab)); - blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue >(tabList)); - cursor.setBlockFormat(blockFormat); - break; - } - default:{ - break; - } - } - }// foreach - } - cursor.setCharFormat(savedCharFormat); // restore the cursor char format -} - -QMap BibliographyGenerator::defaultBibliographyEntryTemplates() -{ - QMap entryTemplates; - foreach (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { - BibliographyEntryTemplate bibEntryTemplate; - - //Now creating default IndexEntries for all BibliographyEntryTemplates - IndexEntryBibliography *identifier = new IndexEntryBibliography(QString()); - IndexEntryBibliography *author = new IndexEntryBibliography(QString()); - IndexEntryBibliography *title = new IndexEntryBibliography(QString()); - IndexEntryBibliography *year = new IndexEntryBibliography(QString()); - IndexEntrySpan *firstSpan = new IndexEntrySpan(QString()); - IndexEntrySpan *otherSpan = new IndexEntrySpan(QString()); - - identifier->dataField = "identifier"; - author->dataField = "author"; - title->dataField = "title"; - year->dataField = "year"; - firstSpan->text = ":"; - otherSpan->text = ","; - - bibEntryTemplate.bibliographyType = bibType; - bibEntryTemplate.indexEntries.append(static_cast(identifier)); - bibEntryTemplate.indexEntries.append(static_cast(firstSpan)); - bibEntryTemplate.indexEntries.append(static_cast(author)); - bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); - bibEntryTemplate.indexEntries.append(static_cast(title)); - bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); - bibEntryTemplate.indexEntries.append(static_cast(year)); - - entryTemplates[bibType] = bibEntryTemplate; - } - return entryTemplates; -} diff --git a/plugins/flake/textshape/kotext/BibliographyGenerator.h b/plugins/flake/textshape/kotext/BibliographyGenerator.h deleted file mode 100644 index bcd78a1ef5..0000000000 --- a/plugins/flake/textshape/kotext/BibliographyGenerator.h +++ /dev/null @@ -1,47 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2011 Smit Patel - - * 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 BIBLIOGRAPHYGENERATOR_H -#define BIBLIOGRAPHYGENERATOR_H - -#include -#include "kritatext_export.h" - -#include - -class KRITATEXT_EXPORT BibliographyGenerator : public QObject, public BibliographyGeneratorInterface -{ - Q_OBJECT -public: - explicit BibliographyGenerator(QTextDocument *bibDocument, const QTextBlock &block, KoBibliographyInfo *bibInfo); - ~BibliographyGenerator() override; - - static QMap defaultBibliographyEntryTemplates(); - -public Q_SLOTS: - void generate(); - -private: - QTextDocument *m_document; - QTextDocument *m_bibDocument; - KoBibliographyInfo *m_bibInfo; - QTextBlock m_block; - qreal m_maxTabPosition; -}; - -#endif diff --git a/plugins/flake/textshape/kotext/CMakeLists.txt b/plugins/flake/textshape/kotext/CMakeLists.txt index c8743a5852..4c93537505 100644 --- a/plugins/flake/textshape/kotext/CMakeLists.txt +++ b/plugins/flake/textshape/kotext/CMakeLists.txt @@ -1,135 +1,134 @@ include_directories(${FONTCONFIG_INCLUDE_DIR} ${FREETYPE_INCLUDE_DIRS}) set(kritatext_LIB_SRCS KoDocumentRdfBase.cpp KoText.cpp KoTextBlockData.cpp KoTextBlockBorderData.cpp KoTextBlockPaintStrategyBase.cpp KoTextOdfSaveHelper.cpp KoTextDocument.cpp KoTextEditor.cpp KoTextEditor_undo.cpp KoTextEditor_format.cpp KoList.cpp KoTextEditingRegistry.cpp KoTextEditingFactory.cpp KoTextEditingPlugin.cpp KoTextRangeManager.cpp KoInlineTextObjectManager.cpp KoInlineObjectFactoryBase.cpp KoInlineObjectRegistry.cpp InsertInlineObjectActionBase_p.cpp InsertVariableAction.cpp InsertNamedVariableAction.cpp InsertTextReferenceAction.cpp InsertTextLocator.cpp KoInlineObject.cpp KoTextRange.cpp KoVariable.cpp KoVariableManager.cpp KoNamedVariable.cpp KoSection.cpp KoSectionEnd.cpp KoSectionUtils.cpp KoSectionModel.cpp KoTextLocator.cpp KoTextReference.cpp KoAnchorInlineObject.cpp KoAnchorTextRange.cpp KoTextShapeSavingContext.cpp KoAnnotation.cpp KoAnnotationManager.cpp KoBookmark.cpp KoBookmarkManager.cpp KoInlineNote.cpp KoInlineCite.cpp KoTextSoftPageBreak.cpp KoTextDebug.cpp KoTextPage.cpp KoPageProvider.cpp KoTableColumnAndRowStyleManager.cpp KoTextInlineRdf.cpp KoTextMeta.cpp KoTextTableTemplate.cpp OdfTextTrackStyles.cpp ToCBibGeneratorInfo.cpp KoTableOfContentsGeneratorInfo.cpp KoBibliographyInfo.cpp - BibliographyGenerator.cpp styles/Styles_p.cpp styles/KoCharacterStyle.cpp styles/KoParagraphStyle.cpp styles/KoStyleManager.cpp styles/KoListStyle.cpp styles/KoListLevelProperties.cpp styles/KoTableStyle.cpp styles/KoTableColumnStyle.cpp styles/KoTableRowStyle.cpp styles/KoTableCellStyle.cpp styles/KoSectionStyle.cpp opendocument/KoTextSharedLoadingData.cpp opendocument/KoTextSharedSavingData.cpp opendocument/KoTextLoader.cpp opendocument/KoTextWriter_p.cpp opendocument/KoTextWriter.cpp changetracker/KoChangeTracker.cpp changetracker/KoChangeTrackerElement.cpp changetracker/KoFormatChangeInformation.cpp changetracker/KoDeletedRowColumnDataStore.cpp changetracker/KoDeletedRowData.cpp changetracker/KoDeletedColumnData.cpp changetracker/KoDeletedCellData.cpp commands/ChangeAnchorPropertiesCommand.cpp commands/ChangeListCommand.cpp commands/ChangeStylesCommand.cpp commands/ChangeStylesMacroCommand.cpp commands/DeleteAnchorsCommand.cpp commands/DeleteAnnotationsCommand.cpp commands/DeleteCommand.cpp commands/DeleteTableColumnCommand.cpp commands/DeleteTableRowCommand.cpp commands/InsertNoteCommand.cpp commands/InsertTableColumnCommand.cpp commands/InsertTableRowCommand.cpp commands/ResizeTableCommand.cpp commands/InsertInlineObjectCommand.cpp commands/ListItemNumberingCommand.cpp commands/TextPasteCommand.cpp commands/AddTextRangeCommand.cpp commands/AddAnnotationCommand.cpp commands/ParagraphFormattingCommand.cpp commands/RenameSectionCommand.cpp commands/NewSectionCommand.cpp commands/SplitSectionsCommand.cpp KoTextDrag.cpp KoTextCommandBase.cpp TextDebug.cpp ) add_library(kritatext SHARED ${kritatext_LIB_SRCS}) generate_export_header(kritatext BASE_NAME kritatext) target_link_libraries(kritatext kritaflake Qt5::Gui kritawidgetutils ) target_link_libraries(kritatext LINK_INTERFACE_LIBRARIES kritaflake Qt5::Gui ) target_include_directories(kritatext PUBLIC $ $ $ ) set_target_properties(kritatext PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritatext ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/plugins/flake/textshape/kotext/KoTextEditor.cpp b/plugins/flake/textshape/kotext/KoTextEditor.cpp index 30eb8ec02f..cf2ac88238 100644 --- a/plugins/flake/textshape/kotext/KoTextEditor.cpp +++ b/plugins/flake/textshape/kotext/KoTextEditor.cpp @@ -1,1588 +1,1585 @@ /* This file is part of the KDE project * Copyright (C) 2009-2012 Pierre Stirnweiss * Copyright (C) 2006-2010 Thomas Zander * Copyright (c) 2011 Boudewijn Rempt * Copyright (C) 2011-2015 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * Copyright (C) 2015 Soma Schliszka * * 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 "KoTextEditor.h" #include "KoTextEditor_p.h" #include "KoList.h" #include "KoBookmark.h" #include "KoAnnotation.h" #include "KoTextRangeManager.h" #include "KoInlineTextObjectManager.h" #include "KoInlineNote.h" #include "KoInlineCite.h" -#include "BibliographyGenerator.h" #include #include #include #include #include #include "KoShapeAnchor.h" #include "KoTextDocument.h" #include "KoTextLocator.h" #include "KoTableOfContentsGeneratorInfo.h" #include "KoBibliographyInfo.h" #include "changetracker/KoChangeTracker.h" #include "changetracker/KoChangeTrackerElement.h" #include "styles/KoCharacterStyle.h" #include "styles/KoParagraphStyle.h" #include "styles/KoStyleManager.h" #include "styles/KoTableCellStyle.h" #include "styles/KoTableStyle.h" #include "KoTableColumnAndRowStyleManager.h" #include "commands/DeleteTableRowCommand.h" #include "commands/DeleteTableColumnCommand.h" #include "commands/InsertTableRowCommand.h" #include "commands/InsertTableColumnCommand.h" #include "commands/ResizeTableCommand.h" #include "commands/TextPasteCommand.h" #include "commands/ListItemNumberingCommand.h" #include "commands/ChangeListCommand.h" #include "commands/InsertInlineObjectCommand.h" #include "commands/DeleteCommand.h" #include "commands/DeleteAnchorsCommand.h" #include "commands/DeleteAnnotationsCommand.h" #include "commands/InsertNoteCommand.h" #include "commands/AddTextRangeCommand.h" #include "commands/AddAnnotationCommand.h" #include "commands/RenameSectionCommand.h" #include "commands/NewSectionCommand.h" #include "commands/SplitSectionsCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" #include "KoTextDebug.h" Q_DECLARE_METATYPE(QTextFrame*) /*Private*/ KoTextEditor::Private::Private(KoTextEditor *qq, QTextDocument *document) : q(qq) , document (document) , addNewCommand(true) , dummyMacroAdded(false) , customCommandCount(0) , editProtectionCached(false) { caret = QTextCursor(document); editorState = NoOp; } void KoTextEditor::Private::emitTextFormatChanged() { emit q->textFormatChanged(); } void KoTextEditor::Private::newLine(KUndo2Command *parent) { // Handle if this is the special block before a table bool hiddenTableHandling = caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable); if (hiddenTableHandling) { // Easy solution is to go back to the end of previous block and do the insertion from there. // However if there is no block before we have a problem. This may be the case if there is // a table before or we are at the beginning of a cell or a document. // So here is a better approach // 1) create block // 2) select the previous block so it gets deleted and replaced // 3) remove HiddenByTable from both new and previous block // 4) actually make new line replacing the block we just inserted // 5) set HiddenByTable on the block just before the table again caret.insertText("oops you should never see this"); caret.insertBlock(); caret.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); caret.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); QTextBlockFormat bf = caret.blockFormat(); bf.clearProperty(KoParagraphStyle::HiddenByTable); caret.setBlockFormat(bf); } if (caret.hasSelection()) { q->deleteChar(false, parent); } KoTextDocument textDocument(document); KoStyleManager *styleManager = textDocument.styleManager(); KoParagraphStyle *nextStyle = 0; KoParagraphStyle *currentStyle = 0; if (styleManager) { int id = caret.blockFormat().intProperty(KoParagraphStyle::StyleId); currentStyle = styleManager->paragraphStyle(id); if (currentStyle == 0) // not a style based parag. Lets make the next one correct. nextStyle = styleManager->defaultParagraphStyle(); else nextStyle = styleManager->paragraphStyle(currentStyle->nextStyle()); Q_ASSERT(nextStyle); if (currentStyle == nextStyle) nextStyle = 0; } QTextCharFormat format = caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } // Build the block format and subtract the properties that are not inherited QTextBlockFormat bf = caret.blockFormat(); bf.clearProperty(KoParagraphStyle::BreakBefore); bf.clearProperty(KoParagraphStyle::ListStartValue); bf.clearProperty(KoParagraphStyle::UnnumberedListItem); bf.clearProperty(KoParagraphStyle::IsListHeader); bf.clearProperty(KoParagraphStyle::MasterPageName); bf.clearProperty(KoParagraphStyle::OutlineLevel); bf.clearProperty(KoParagraphStyle::HiddenByTable); // We should stay in the same section so we can't start new one. bf.clearProperty(KoParagraphStyle::SectionStartings); // But we move all the current endings to the next paragraph. QTextBlockFormat origin = caret.blockFormat(); origin.clearProperty(KoParagraphStyle::SectionEndings); caret.setBlockFormat(origin); // Build the block char format which is just a copy QTextCharFormat bcf = caret.blockCharFormat(); // Actually insert the new paragraph char int startPosition = caret.position(); caret.insertBlock(bf, bcf); int endPosition = caret.position(); // Mark the CR as a tracked change QTextCursor changeCursor(document); changeCursor.beginEditBlock(); changeCursor.setPosition(startPosition); changeCursor.setPosition(endPosition, QTextCursor::KeepAnchor); changeCursor.endEditBlock(); q->registerTrackedChange(changeCursor, KoGenChange::InsertChange, kundo2_i18n("New Paragraph"), format, format, false); // possibly change the style if requested if (nextStyle) { QTextBlock block = caret.block(); if (currentStyle) currentStyle->unapplyStyle(block); nextStyle->applyStyle(block); format = block.charFormat(); } caret.setCharFormat(format); if (hiddenTableHandling) { // see code and comment above QTextBlockFormat bf = caret.blockFormat(); bf.setProperty(KoParagraphStyle::HiddenByTable, true); caret.setBlockFormat(bf); caret.movePosition(QTextCursor::PreviousCharacter); } } /*KoTextEditor*/ //TODO factor out the changeTracking charFormat setting from all individual slots to a public slot, which will be available for external commands (TextShape) //The BlockFormatVisitor and CharFormatVisitor are used when a property needs to be modified relative to its current value (which could be different over the selection). For example: increase indentation by 10pt. //The BlockFormatVisitor is also used for the change tracking of a blockFormat. The changeTracker stores the information about the changeId in the charFormat. The BlockFormatVisitor ensures that thd changeId is set on the whole block (even if only a part of the block is actually selected). //Should such mechanisms be later provided directly by Qt, we could dispose of these classes. KoTextEditor::KoTextEditor(QTextDocument *document) : QObject(document), d (new Private(this, document)) { connect (d->document, SIGNAL (undoCommandAdded()), this, SLOT (documentCommandAdded())); } KoTextEditor::~KoTextEditor() { delete d; } KoTextEditor *KoTextEditor::getTextEditorFromCanvas(KoCanvasBase *canvas) { KoSelection *selection = canvas->shapeManager()->selection(); if (selection) { Q_FOREACH (KoShape *shape, selection->selectedShapes()) { if (KoTextShapeDataBase *textData = qobject_cast(shape->userData())) { KoTextDocument doc(textData->document()); return doc.textEditor(); } } } return 0; } QTextCursor* KoTextEditor::cursor() { return &(d->caret); } const QTextCursor KoTextEditor::constCursor() const { return QTextCursor(d->caret); } void KoTextEditor::registerTrackedChange(QTextCursor &/*selection*/, KoGenChange::Type /*changeType*/, const KUndo2MagicString &/*title*/, QTextFormat& /*format*/, QTextFormat& /*prevFormat*/, bool /*applyToWholeBlock*/) { } // To figure out if a the blocks of the selection are write protected we need to // traverse the entire document as sections build up the protectiveness recursively. void KoTextEditor::recursivelyVisitSelection(QTextFrame::iterator it, KoTextVisitor &visitor) const { do { if (visitor.abortVisiting()) return; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); if (table) { // There are 4 ways this table can be selected: // - "before to mid" // - "mid to after" // - "complex mid to mid" // - "simple mid to mid" // The 3 first are entire cells, the fourth is within a cell if (d->caret.selectionStart() <= table->lastPosition() && d->caret.selectionEnd() >= table->firstPosition()) { // We have a selection somewhere QTextTableCell cell1 = table->cellAt(d->caret.selectionStart()); QTextTableCell cell2 = table->cellAt(d->caret.selectionEnd()); if (cell1 != cell2 || !cell1.isValid() || !cell2.isValid()) { // And the selection is complex or entire table int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; if (!cell1.isValid() || !cell2.isValid()) { // entire table visitor.visitTable(table, KoTextVisitor::Entirely); selectionRow = selectionColumn = 0; selectionRowSpan = table->rows(); selectionColumnSpan = table->columns(); } else { visitor.visitTable(table, KoTextVisitor::Partly); d->caret.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); } for (int r = selectionRow; r < selectionRow + selectionRowSpan; r++) { for (int c = selectionColumn; c < selectionColumn + selectionColumnSpan; c++) { QTextTableCell cell = table->cellAt(r,c); if (!cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { visitor.visitTableCell(&cell, KoTextVisitor::Partly); recursivelyVisitSelection(cell.begin(), visitor); } else { visitor.nonVisit(); } if (visitor.abortVisiting()) return; } } } else { visitor.visitTable(table, KoTextVisitor::Partly); // And the selection is simple if (!cell1.format().boolProperty(KoTableCellStyle::CellIsProtected)) { visitor.visitTableCell(&cell1, KoTextVisitor::Entirely); recursivelyVisitSelection(cell1.begin(), visitor); } else { visitor.nonVisit(); } return; } } if (d->caret.selectionEnd() <= table->lastPosition()) { return; } } else if (subFrame) { recursivelyVisitSelection(subFrame->begin(), visitor); } else { // TODO build up the section stack if (d->caret.selectionStart() < block.position() + block.length() && d->caret.selectionEnd() >= block.position()) { // We have a selection somewhere if (true) { // TODO don't change if block is protected by section visitor.visitBlock(block, d->caret); } else { visitor.nonVisit(); } } // TODO tear down the section stack if (d->caret.selectionEnd() < block.position() + block.length()) { return; } } if (!it.atEnd()) { ++it; } } while (!it.atEnd()); } KoBookmark *KoTextEditor::addBookmark(const QString &name) {//TODO changeTracking KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Bookmark")); KoBookmark *bookmark = new KoBookmark(d->caret); bookmark->setName(name); bookmark->setManager(KoTextDocument(d->document).textRangeManager()); addCommand(new AddTextRangeCommand(bookmark, topCommand)); endEditBlock(); return bookmark; } KoTextRangeManager *KoTextEditor::textRangeManager() const { return KoTextDocument(d->document).textRangeManager(); } KoAnnotation *KoTextEditor::addAnnotation(KoShape *annotationShape) { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Annotation")); KoAnnotation *annotation = new KoAnnotation(d->caret); KoTextRangeManager *textRangeManager = KoTextDocument(d->document).textRangeManager(); annotation->setManager(textRangeManager); //FIXME: I need the name, a unique name, we can set selected text as annotation name or use createUniqueAnnotationName function // to do it for us. QString name = annotation->createUniqueAnnotationName(textRangeManager->annotationManager(), "", false); annotation->setName(name); annotation->setAnnotationShape(annotationShape); addCommand(new AddAnnotationCommand(annotation, topCommand)); endEditBlock(); return annotation; } KoInlineObject *KoTextEditor::insertIndexMarker() {//TODO changeTracking if (isEditProtected()) { return 0; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Index")); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); } QTextBlock block = d->caret.block(); if (d->caret.position() >= block.position() + block.length() - 1) return 0; // can't insert one at end of text if (block.text()[ d->caret.position() - block.position()].isSpace()) return 0; // can't insert one on a whitespace as that does not indicate a word. KoTextLocator *tl = new KoTextLocator(); KoTextDocument(d->document).inlineTextObjectManager()->insertInlineObject(d->caret, tl); d->updateState(KoTextEditor::Private::NoOp); return tl; } void KoTextEditor::insertInlineObject(KoInlineObject *inliner, KUndo2Command *cmd) { if (isEditProtected()) { return; } KUndo2Command *topCommand = cmd; if (!cmd) { topCommand = beginEditBlock(kundo2_i18n("Insert Variable")); } if (d->caret.hasSelection()) { deleteChar(false, topCommand); } d->caret.beginEditBlock(); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); } QTextCharFormat format = d->caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } InsertInlineObjectCommand *insertInlineObjectCommand = new InsertInlineObjectCommand(inliner, d->document, topCommand); Q_UNUSED(insertInlineObjectCommand); d->caret.endEditBlock(); if (!cmd) { addCommand(topCommand); endEditBlock(); } emit cursorPositionChanged(); } void KoTextEditor::updateInlineObjectPosition(int start, int end) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(d->document).inlineTextObjectManager(); // and, of course, every inline object after the current position has the wrong position QTextCursor cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), start); while (!cursor.isNull() && (end > -1 && cursor.position() < end )) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *obj = inlineObjectManager->inlineTextObject(fmt); obj->updatePosition(d->document, cursor.position(), fmt); cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), cursor.position()); } } void KoTextEditor::removeAnchors(const QList &anchors, KUndo2Command *parent) { Q_ASSERT(parent); addCommand(new DeleteAnchorsCommand(anchors, d->document, parent)); } void KoTextEditor::removeAnnotations(const QList &annotations, KUndo2Command *parent) { Q_ASSERT(parent); addCommand(new DeleteAnnotationsCommand(annotations, d->document, parent)); } void KoTextEditor::insertFrameBreak() { if (isEditProtected()) { return; } QTextCursor curr(d->caret.block()); if (dynamic_cast (curr.currentFrame())) { return; } d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Insert Break")); QTextBlock block = d->caret.block(); if (d->caret.position() == block.position() && block.length() > 0) { // start of parag QTextBlockFormat bf = d->caret.blockFormat(); bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); d->caret.insertBlock(bf); if (block.textList()) block.textList()->remove(block); } else { QTextBlockFormat bf = d->caret.blockFormat(); if (!d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { newLine(); } bf = d->caret.blockFormat(); bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); d->caret.setBlockFormat(bf); } d->updateState(KoTextEditor::Private::NoOp); emit cursorPositionChanged(); } void KoTextEditor::paste(KoCanvasBase *canvas, const QMimeData *mimeData, bool pasteAsText) { if (isEditProtected()) { return; } KoShapeController *shapeController = KoTextDocument(d->document).shapeController(); addCommand(new TextPasteCommand(mimeData, d->document, shapeController, canvas, 0, pasteAsText)); } void KoTextEditor::deleteChar(bool previous, KUndo2Command *parent) { if (isEditProtected()) { return; } KoShapeController *shapeController = KoTextDocument(d->document).shapeController(); // Find out if we should track changes or not // KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); // bool trackChanges = false; // if (changeTracker && changeTracker->recordChanges()) { // trackChanges = true; // } if (previous) { if (!d->caret.hasSelection() && d->caret.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { movePosition(QTextCursor::PreviousCharacter); if (d->caret.block().length() <= 1) { movePosition(QTextCursor::NextCharacter); } else return; // it becomes just a cursor movement; } } else { if (!d->caret.hasSelection() && d->caret.block().length() > 1) { QTextCursor tmpCursor = d->caret; tmpCursor.movePosition(QTextCursor::NextCharacter); if (tmpCursor.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { movePosition(QTextCursor::NextCharacter); return; // it becomes just a cursor movement; } } } if (previous) { addCommand(new DeleteCommand(DeleteCommand::PreviousChar, d->document, shapeController, parent)); } else { addCommand(new DeleteCommand(DeleteCommand::NextChar, d->document, shapeController, parent)); } } void KoTextEditor::toggleListNumbering(bool numberingEnabled) { if (isEditProtected()) { return; } addCommand(new ListItemNumberingCommand(block(), numberingEnabled)); emit textFormatChanged(); } void KoTextEditor::setListProperties(const KoListLevelProperties &llp, ChangeListFlags flags, KUndo2Command *parent) { if (isEditProtected()) { return; } if (flags & AutoListStyle && d->caret.block().textList() == 0) { flags = MergeWithAdjacentList; } if (KoList *list = KoTextDocument(d->document).list(d->caret.block().textList())) { KoListStyle *listStyle = list->style(); if (KoStyleManager *styleManager = KoTextDocument(d->document).styleManager()) { QList paragraphStyles = styleManager->paragraphStyles(); foreach (KoParagraphStyle *paragraphStyle, paragraphStyles) { if (paragraphStyle->listStyle() == listStyle || (paragraphStyle->list() && paragraphStyle->list()->style() == listStyle)) { flags = NoFlags; break; } } } } addCommand(new ChangeListCommand(d->caret, llp, flags, parent)); emit textFormatChanged(); } int KoTextEditor::anchor() const { return d->caret.anchor(); } bool KoTextEditor::atBlockEnd() const { return d->caret.atBlockEnd(); } bool KoTextEditor::atBlockStart() const { return d->caret.atBlockStart(); } bool KoTextEditor::atEnd() const { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { //auxFrame really is the auxiliary frame if (d->caret.position() == auxFrame->firstPosition() - 1) { return true; } return false; } return d->caret.atEnd(); } bool KoTextEditor::atStart() const { return d->caret.atStart(); } QTextBlock KoTextEditor::block() const { return d->caret.block(); } int KoTextEditor::blockNumber() const { return d->caret.blockNumber(); } void KoTextEditor::clearSelection() { d->caret.clearSelection(); } int KoTextEditor::columnNumber() const { return d->caret.columnNumber(); } void KoTextEditor::deleteChar() { if (isEditProtected()) { return; } if (!d->caret.hasSelection()) { if (d->caret.atEnd()) return; // We alson need to refuse delete if we are at final pos in table cell if (QTextTable *table = d->caret.currentTable()) { QTextTableCell cell = table->cellAt(d->caret.position()); if (d->caret.position() == cell.lastCursorPosition().position()) { return; } } // We also need to refuse delete if it will delete a note frame QTextCursor after(d->caret); after.movePosition(QTextCursor::NextCharacter); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame != afterFrame) { return; } } deleteChar(false); emit cursorPositionChanged(); } void KoTextEditor::deletePreviousChar() { if (isEditProtected()) { return; } if (!d->caret.hasSelection()) { if (d->caret.atStart()) return; // We also need to refuse delete if we are at first pos in table cell if (QTextTable *table = d->caret.currentTable()) { QTextTableCell cell = table->cellAt(d->caret.position()); if (d->caret.position() == cell.firstCursorPosition().position()) { return; } } // We also need to refuse delete if it will delete a note frame QTextCursor after(d->caret); after.movePosition(QTextCursor::PreviousCharacter); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame != afterFrame) { return; } } deleteChar(true); emit cursorPositionChanged(); } QTextDocument *KoTextEditor::document() const { return d->caret.document(); } bool KoTextEditor::hasComplexSelection() const { return d->caret.hasComplexSelection(); } bool KoTextEditor::hasSelection() const { return d->caret.hasSelection(); } class ProtectionCheckVisitor : public KoTextVisitor { public: ProtectionCheckVisitor(const KoTextEditor *editor) : KoTextVisitor(const_cast(editor)) { } // override super's implementation to not waste cpu cycles void visitBlock(QTextBlock&, const QTextCursor &) override { } void nonVisit() override { setAbortVisiting(true); } }; bool KoTextEditor::isEditProtected(bool useCached) const { ProtectionCheckVisitor visitor(this); if (useCached) { if (! d->editProtectionCached) { recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); d->editProtected = visitor.abortVisiting(); d->editProtectionCached = true; } return d->editProtected; } d->editProtectionCached = false; recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); return visitor.abortVisiting(); } void KoTextEditor::insertTable(int rows, int columns) { if (isEditProtected() || rows <= 0 || columns <= 0) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextTableFormat tableFormat; tableFormat.setWidth(QTextLength(QTextLength::PercentageLength, 100)); tableFormat.setProperty(KoTableStyle::CollapsingBorders, true); tableFormat.setMargin(5); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Table"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } tableFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } QTextBlock currentBlock = d->caret.block(); if (d->caret.position() != currentBlock.position()) { d->caret.insertBlock(); currentBlock = d->caret.block(); } QTextTable *table = d->caret.insertTable(rows, columns, tableFormat); // Get (and thus create) columnandrowstyle manager so it becomes part of undo // and not something that happens uncontrollably during layout KoTableColumnAndRowStyleManager::getManager(table); // 'Hide' the block before the table QTextBlockFormat blockFormat = currentBlock.blockFormat(); QTextCursor cursor(currentBlock); blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true); cursor.setBlockFormat(blockFormat); // Define the initial cell format QTextTableCellFormat format; KoTableCellStyle cellStyle; cellStyle.setEdge(KoBorder::TopBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::LeftBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::BottomBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::RightBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setPadding(5); cellStyle.applyStyle(format); // Apply formatting to all cells for (int row = 0; row < table->rows(); ++row) { for (int col = 0; col < table->columns(); ++col) { QTextTableCell cell = table->cellAt(row, col); cell.setFormat(format); } } if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } void KoTextEditor::insertTableRowAbove() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableRowCommand(this, table, false)); } } void KoTextEditor::insertTableRowBelow() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableRowCommand(this, table, true)); } } void KoTextEditor::insertTableColumnLeft() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableColumnCommand(this, table, false)); } } void KoTextEditor::insertTableColumnRight() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableColumnCommand(this, table, true)); } } void KoTextEditor::deleteTableColumn() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new DeleteTableColumnCommand(this, table)); } } void KoTextEditor::deleteTableRow() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new DeleteTableRowCommand(this, table)); } } void KoTextEditor::mergeTableCells() { if (isEditProtected()) { return; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Merge Cells")); QTextTable *table = d->caret.currentTable(); if (table) { table->mergeCells(d->caret); } d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::splitTableCells() { if (isEditProtected()) { return; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Split Cells")); QTextTable *table = d->caret.currentTable(); if (table) { QTextTableCell cell = table->cellAt(d->caret); table->splitCell(cell.row(), cell.column(), 1, 1); } d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::adjustTableColumnWidth(QTextTable *table, int column, qreal width, KUndo2Command *parentCommand) { ResizeTableCommand *cmd = new ResizeTableCommand(table, true, column, width, parentCommand); addCommand(cmd); } void KoTextEditor::adjustTableRowHeight(QTextTable *table, int column, qreal height, KUndo2Command *parentCommand) { ResizeTableCommand *cmd = new ResizeTableCommand(table, false, column, height, parentCommand); addCommand(cmd); } void KoTextEditor::adjustTableWidth(QTextTable *table, qreal dLeft, qreal dRight) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Adjust Table Width")); d->caret.beginEditBlock(); QTextTableFormat fmt = table->format(); if (dLeft) { fmt.setLeftMargin(fmt.leftMargin() + dLeft); } if (dRight) { fmt.setRightMargin(fmt.rightMargin() + dRight); } table->setFormat(fmt); d->caret.endEditBlock(); d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::setTableBorderData(QTextTable *table, int row, int column, KoBorder::BorderSide cellSide, const KoBorder::BorderData &data) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Change Border Formatting")); d->caret.beginEditBlock(); QTextTableCell cell = table->cellAt(row, column); QTextCharFormat fmt = cell.format(); KoBorder border = fmt.property(KoTableCellStyle::Borders).value(); border.setBorderData(cellSide, data); fmt.setProperty(KoTableCellStyle::Borders, QVariant::fromValue(border)); cell.setFormat(fmt); d->caret.endEditBlock(); d->updateState(KoTextEditor::Private::NoOp); } KoInlineNote *KoTextEditor::insertFootNote() { if (isEditProtected()) { return 0; } InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Footnote, d->document); addCommand(cmd); emit cursorPositionChanged(); return cmd->m_inlineNote; } KoInlineNote *KoTextEditor::insertEndNote() { if (isEditProtected()) { return 0; } InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Endnote, d->document); addCommand(cmd); emit cursorPositionChanged(); return cmd->m_inlineNote; } void KoTextEditor::insertTableOfContents(KoTableOfContentsGeneratorInfo *info) { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table Of Contents")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table Of Contents")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextBlockFormat tocFormat; KoTableOfContentsGeneratorInfo *newToCInfo = info->clone(); QTextDocument *tocDocument = new QTextDocument(); tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(newToCInfo) ); tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(tocDocument)); //make sure we set up the textrangemanager on the subdocument as well KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Table Of Contents"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } tocFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } d->caret.insertBlock(); d->caret.movePosition(QTextCursor::Left); d->caret.insertBlock(tocFormat); d->caret.movePosition(QTextCursor::Right); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } void KoTextEditor::setTableOfContentsConfig(KoTableOfContentsGeneratorInfo *info, const QTextBlock &block) { if (isEditProtected()) { return; } KoTableOfContentsGeneratorInfo *newToCInfo=info->clone(); d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Modify Table Of Contents")); QTextCursor cursor(block); QTextBlockFormat tocBlockFormat=block.blockFormat(); tocBlockFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(newToCInfo) ); cursor.setBlockFormat(tocBlockFormat); d->updateState(KoTextEditor::Private::NoOp); emit cursorPositionChanged(); const_cast(document())->markContentsDirty(document()->firstBlock().position(), 0); } void KoTextEditor::insertBibliography(KoBibliographyInfo *info) { bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Bibliography")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Bibliography")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextBlockFormat bibFormat; KoBibliographyInfo *newBibInfo = info->clone(); QTextDocument *bibDocument = new QTextDocument(); bibFormat.setProperty( KoParagraphStyle::BibliographyData, QVariant::fromValue(newBibInfo)); bibFormat.setProperty( KoParagraphStyle::GeneratedDocument, QVariant::fromValue(bibDocument)); //make sure we set up the textrangemanager on the subdocument as well KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Bibliography"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } bibFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } d->caret.insertBlock(); d->caret.movePosition(QTextCursor::Left); d->caret.insertBlock(bibFormat); d->caret.movePosition(QTextCursor::Right); - new BibliographyGenerator(bibDocument, block(), newBibInfo); - if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } KoInlineCite *KoTextEditor::insertCitation() { bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Add Citation")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Citation")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation); KoInlineTextObjectManager *manager = KoTextDocument(d->document).inlineTextObjectManager(); manager->insertInlineObject(d->caret,cite); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } return cite; } void KoTextEditor::insertText(const QString &text, const QString &hRef) { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Typing")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Typing")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } //first we make sure that we clear the inlineObject charProperty, if we have no selection if (!hasSelection && d->caret.charFormat().hasProperty(KoCharacterStyle::InlineInstanceId)) d->clearCharFormatProperty(KoCharacterStyle::InlineInstanceId); int startPosition = d->caret.position(); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); startPosition = d->caret.position(); } QTextCharFormat format = d->caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } static QRegExp urlScanner("\\S+://\\S+"); if (!hRef.isEmpty()) { format.setAnchor(true); format.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); if ((urlScanner.indexIn(hRef)) == 0) {//web url format.setAnchorHref(hRef); } else { format.setAnchorHref("#"+hRef); } } d->caret.insertText(text, format); int endPosition = d->caret.position(); //Mark the inserted text d->caret.setPosition(startPosition); d->caret.setPosition(endPosition, QTextCursor::KeepAnchor); registerTrackedChange(d->caret, KoGenChange::InsertChange, kundo2_i18n("Typing"), format, format, false); d->caret.clearSelection(); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } if (!hRef.isEmpty()) { format.setAnchor(false); format.clearProperty(KoCharacterStyle::Anchor); format.clearProperty(KoCharacterStyle::AnchorType); d->caret.setCharFormat(format); } emit cursorPositionChanged(); } void KoTextEditor::insertHtml(const QString &html) { if (isEditProtected()) { return; } // XXX: do the changetracking and everything! QTextBlock currentBlock = d->caret.block(); d->caret.insertHtml(html); QList pastedLists; KoList *currentPastedList = 0; while (currentBlock != d->caret.block()) { currentBlock = currentBlock.next(); QTextList *currentTextList = currentBlock.textList(); if(currentTextList && !pastedLists.contains(currentBlock.textList())) { KoListStyle *listStyle = KoTextDocument(d->document).styleManager()->defaultListStyle()->clone(); listStyle->setName(QString()); listStyle->setStyleId(0); currentPastedList = new KoList(d->document, listStyle); QTextListFormat currentTextListFormat = currentTextList->format(); KoListLevelProperties levelProperty = listStyle->levelProperties(currentTextListFormat.indent()); levelProperty.setStyle(static_cast(currentTextListFormat.style())); levelProperty.setLevel(currentTextListFormat.indent()); levelProperty.setListItemPrefix(QString()); levelProperty.setListItemSuffix(QString()); levelProperty.setListId((KoListStyle::ListIdType)currentTextList); listStyle->setLevelProperties(levelProperty); currentTextListFormat.setProperty(KoListStyle::Level, currentTextListFormat.indent()); currentBlock.textList()->setFormat(currentTextListFormat); currentPastedList->updateStoredList(currentBlock); currentPastedList->setStyle(listStyle); pastedLists.append(currentBlock.textList()); } } } bool KoTextEditor::movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n) { d->editProtectionCached = false; // We need protection against moving in and out of note areas QTextCursor after(d->caret); bool b = after.movePosition (operation, mode, n); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame == afterFrame) { if (after.selectionEnd() == after.document()->characterCount() -1) { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (operation == QTextCursor::End) { d->caret.setPosition(auxFrame->firstPosition() - 1, mode); emit cursorPositionChanged(); return true; } return false; } } d->caret = after; emit cursorPositionChanged(); return b; } return false; } void KoTextEditor::newSection() { if (isEditProtected()) { return; } NewSectionCommand *cmd = new NewSectionCommand(d->document); addCommand(cmd); emit cursorPositionChanged(); } void KoTextEditor::splitSectionsStartings(int sectionIdToInsertBefore) { if (isEditProtected()) { return; } addCommand(new SplitSectionsCommand( d->document, SplitSectionsCommand::Startings, sectionIdToInsertBefore)); emit cursorPositionChanged(); } void KoTextEditor::splitSectionsEndings(int sectionIdToInsertAfter) { if (isEditProtected()) { return; } addCommand(new SplitSectionsCommand( d->document, SplitSectionsCommand::Endings, sectionIdToInsertAfter)); emit cursorPositionChanged(); } void KoTextEditor::renameSection(KoSection* section, const QString &newName) { if (isEditProtected()) { return; } addCommand(new RenameSectionCommand(section, newName, document())); } void KoTextEditor::newLine() { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("New Paragraph")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("New Paragraph")); deleteChar(false, topCommand); } d->caret.beginEditBlock(); d->newLine(0); d->caret.endEditBlock(); if (hasSelection) { endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } class WithinSelectionVisitor : public KoTextVisitor { public: WithinSelectionVisitor(KoTextEditor *editor, int position) : KoTextVisitor(editor) , m_position(position) , m_returnValue(false) { } void visitBlock(QTextBlock &block, const QTextCursor &caret) override { if (m_position >= qMax(block.position(), caret.selectionStart()) && m_position <= qMin(block.position() + block.length(), caret.selectionEnd())) { m_returnValue = true; setAbortVisiting(true); } } int m_position; //the position we are searching for bool m_returnValue; //if position is within the selection }; bool KoTextEditor::isWithinSelection(int position) const { // we know the visitor doesn't do anything with the texteditor so let's const cast // to have a more beautiful outer api WithinSelectionVisitor visitor(const_cast(this), position); recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); return visitor.m_returnValue; } int KoTextEditor::position() const { return d->caret.position(); } void KoTextEditor::select(QTextCursor::SelectionType selection) { //TODO add selection of previous/next char, and option about hasSelection d->caret.select(selection); } QString KoTextEditor::selectedText() const { return d->caret.selectedText(); } QTextDocumentFragment KoTextEditor::selection() const { return d->caret.selection(); } int KoTextEditor::selectionEnd() const { return d->caret.selectionEnd(); } int KoTextEditor::selectionStart() const { return d->caret.selectionStart(); } void KoTextEditor::setPosition(int pos, QTextCursor::MoveMode mode) { d->editProtectionCached = false; if (pos == d->caret.document()->characterCount() -1) { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { return; } } if (mode == QTextCursor::MoveAnchor) { d->caret.setPosition (pos, mode); emit cursorPositionChanged(); } // We need protection against moving in and out of note areas QTextCursor after(d->caret); after.setPosition (pos, mode); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame == afterFrame) { d->caret = after; emit cursorPositionChanged(); } } void KoTextEditor::setVisualNavigation(bool b) { d->caret.setVisualNavigation (b); } bool KoTextEditor::visualNavigation() const { return d->caret.visualNavigation(); } const QTextFrame *KoTextEditor::currentFrame () const { return d->caret.currentFrame(); } const QTextList *KoTextEditor::currentList () const { return d->caret.currentList(); } const QTextTable *KoTextEditor::currentTable () const { return d->caret.currentTable(); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoTextEditor.cpp" diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp index d39724d8e4..25c40b9e58 100644 --- a/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp +++ b/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp @@ -1,1652 +1,1529 @@ /* This file is part of the KDE project * Copyright (C) 2001-2006 David Faure * Copyright (C) 2007,2009,2011 Thomas Zander * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2007,2011 Pierre Ducroquet * Copyright (C) 2007-2011 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009-2012 KO GmbH * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 KO GmbH * Copyright (C) 2011 Pavol Korinek * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011-2012 Gopalakrishna Bhat A * Copyright (C) 2012 Inge Wallin * Copyright (C) 2009-2012 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "KoTextLoader.h" #include #include #include #include #include #include #include #include #include #include "KoList.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoTextDebug.h" #include "KoTextDocument.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include #include #include "KoTextInlineRdf.h" #include "KoTableOfContentsGeneratorInfo.h" #include "KoBibliographyInfo.h" #include "KoSection.h" #include "KoSectionEnd.h" #include "KoTextSoftPageBreak.h" #include "KoDocumentRdfBase.h" #include "KoElementReference.h" #include "KoTextTableTemplate.h" #include "styles/KoStyleManager.h" #include "styles/KoParagraphStyle.h" #include "styles/KoCharacterStyle.h" #include "styles/KoListStyle.h" #include "styles/KoListLevelProperties.h" #include "styles/KoTableStyle.h" #include "styles/KoTableColumnStyle.h" #include "styles/KoTableCellStyle.h" #include "styles/KoSectionStyle.h" #include #include #include #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // if defined then debugging is enabled // #define KOOPENDOCUMENTLOADER_DEBUG /// \internal d-pointer class. class Q_DECL_HIDDEN KoTextLoader::Private { public: KoShapeLoadingContext &context; KoTextSharedLoadingData *textSharedData; // store it here so that you don't need to get it all the time from // the KoOdfLoadingContext. bool stylesDotXml; QTextBlockFormat defaultBlockFormat; QTextCharFormat defaultCharFormat; int bodyProgressTotal; int bodyProgressValue; int nextProgressReportMs; QTime progressTime; QVector currentLists; KoListStyle *currentListStyle; int currentListLevel; // Two lists that follow the same style are considered as one for numbering purposes // This hash keeps all the lists that have the same style in one KoList. QHash lists; KoCharacterStyle *endCharStyle; // charstyle from empty span used at end of paragraph KoStyleManager *styleManager; KoShape *shape; int loadSpanLevel; int loadSpanInitialPos; QVector nameSpacesList; QList openingSections; QStack sectionStack; // Used to track the parent of current section QMap xmlIdToListMap; QVector m_previousList; QMap numberedParagraphListId; QStringList rdfIdList; /// level is between 1 and 10 void setCurrentList(KoList *currentList, int level); /// level is between 1 and 10 KoList *previousList(int level); explicit Private(KoShapeLoadingContext &context, KoShape *s) : context(context), textSharedData(0), // stylesDotXml says from where the office:automatic-styles are to be picked from: // the content.xml or the styles.xml (in a multidocument scenario). It does not // decide from where the office:styles are to be picked (always picked from styles.xml). // For our use here, stylesDotXml is always false (see ODF1.1 spec §2.1). stylesDotXml(context.odfLoadingContext().useStylesAutoStyles()), bodyProgressTotal(0), bodyProgressValue(0), nextProgressReportMs(0), currentLists(10), currentListStyle(0), currentListLevel(1), endCharStyle(0), styleManager(0), shape(s), loadSpanLevel(0), loadSpanInitialPos(0) , m_previousList(10) { progressTime.start(); } ~Private() { debugText << "Loading took" << (float)(progressTime.elapsed()) / 1000 << " seconds"; } KoList *list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList); }; KoList *KoTextLoader::Private::list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList) { //TODO: Remove mergeSimilarStyledList parameter by finding a way to put the numbered-paragraphs of same level // to a single QTextList while loading rather than maintaining a hash list if (mergeSimilarStyledList) { if (lists.contains(listStyle)) { return lists[listStyle]; } } KoList *newList = new KoList(document, listStyle); lists[listStyle] = newList; return newList; } void KoTextLoader::Private::setCurrentList(KoList *currentList, int level) { Q_ASSERT(level > 0 && level <= 10); currentLists[level - 1] = currentList; m_previousList[level - 1] = currentList; } KoList *KoTextLoader::Private::previousList(int level) { Q_ASSERT(level > 0 && level <= 10); if (m_previousList.size() < level) { return 0; } return m_previousList.at(level - 1); } inline static bool isspace(ushort ch) { // options are ordered by likelihood return ch == ' ' || ch== '\n' || ch == '\r' || ch == '\t'; } QString KoTextLoader::normalizeWhitespace(const QString &in, bool leadingSpace) { QString textstring = in; ushort *text = (ushort*)textstring.data(); // this detaches from the string 'in' int r, w = 0; int len = textstring.length(); for (r = 0; r < len; ++r) { const ushort ch = text[r]; // check for space, tab, line feed, carriage return if (isspace(ch)) { // if we were lead by whitespace in some parent or previous sibling element, // we completely collapse this space if (r != 0 || !leadingSpace) text[w++] = ' '; // find the end of the whitespace run while (r < len && isspace(text[r])) ++r; // and then record the next non-whitespace character if (r < len) text[w++] = text[r]; } else { text[w++] = ch; } } // and now trim off the unused part of the string textstring.truncate(w); return textstring; } /////////////KoTextLoader KoTextLoader::KoTextLoader(KoShapeLoadingContext &context, KoShape *shape) : QObject() , d(new Private(context, shape)) { KoSharedLoadingData *sharedData = context.sharedData(KOTEXT_SHARED_LOADING_ID); if (sharedData) { d->textSharedData = dynamic_cast(sharedData); } //debugText << "sharedData" << sharedData << "textSharedData" << d->textSharedData; if (!d->textSharedData) { d->textSharedData = new KoTextSharedLoadingData(); KoDocumentResourceManager *rm = context.documentResourceManager(); KoStyleManager *styleManager = rm->resource(KoText::StyleManager).value(); d->textSharedData->loadOdfStyles(context, styleManager); if (!sharedData) { context.addSharedData(KOTEXT_SHARED_LOADING_ID, d->textSharedData); } else { warnText << "A different type of sharedData was found under the" << KOTEXT_SHARED_LOADING_ID; Q_ASSERT(false); } } if (context.documentRdf()) { d->rdfIdList = qobject_cast(context.documentRdf())->idrefList(); } } KoTextLoader::~KoTextLoader() { delete d; } void KoTextLoader::loadBody(const KoXmlElement &bodyElem, QTextCursor &cursor, LoadBodyMode mode) { const QTextDocument *document = cursor.block().document(); static int rootCallChecker = 0; if (rootCallChecker == 0) { if (document->resource(KoTextDocument::FrameCharFormat, KoTextDocument::FrameCharFormatUrl).isValid()) { d->defaultBlockFormat = KoTextDocument(document).frameBlockFormat(); d->defaultCharFormat = KoTextDocument(document).frameCharFormat(); } else { // This is the first call of loadBody on the document. // Store the default block and char formats // Will be used whenever a new block is inserted d->defaultCharFormat = cursor.charFormat(); KoTextDocument(document).setFrameCharFormat(cursor.blockCharFormat()); d->defaultBlockFormat = cursor.blockFormat(); KoTextDocument(document).setFrameBlockFormat(cursor.blockFormat()); } } rootCallChecker++; cursor.beginEditBlock(); // If we are pasting text, we should handle sections correctly // we are saving which sections end in current block // and put their ends after the inserted text. QList oldSectionEndings; if (mode == PasteMode) { QTextBlockFormat fmt = cursor.blockFormat(); oldSectionEndings = KoSectionUtils::sectionEndings(fmt); fmt.clearProperty(KoParagraphStyle::SectionEndings); cursor.setBlockFormat(fmt); } if (!d->openingSections.isEmpty()) { QTextBlockFormat format = cursor.block().blockFormat(); d->openingSections << KoSectionUtils::sectionStartings(format); // if we had some already we need to append the new ones KoSectionUtils::setSectionStartings(format, d->openingSections); cursor.setBlockFormat(format); d->openingSections.clear(); } KoOdfLineNumberingConfiguration *lineNumberingConfiguration = new KoOdfLineNumberingConfiguration(d->context.odfLoadingContext() .stylesReader() .lineNumberingConfiguration()); KoTextDocument(document).setLineNumberingConfiguration(lineNumberingConfiguration); KoOdfBibliographyConfiguration *bibConfiguration = new KoOdfBibliographyConfiguration(d->context.odfLoadingContext() .stylesReader() .globalBibliographyConfiguration()); KoTextDocument(document).styleManager()->setBibliographyConfiguration(bibConfiguration); d->styleManager = KoTextDocument(document).styleManager(); #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif bool usedParagraph = false; // set to true if we found a tag that used the paragraph, indicating that the next round needs to start a new one. if (bodyElem.namespaceURI() == KoXmlNS::table && bodyElem.localName() == "table") { loadTable(bodyElem, cursor); } else { startBody(KoXml::childNodesCount(bodyElem)); KoXmlElement tag; for (KoXmlNode _node = bodyElem.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { if (!(tag = _node.toElement()).isNull()) { const QString localName = tag.localName(); if (tag.namespaceURI() == KoXmlNS::text) { if ((usedParagraph) && (tag.localName() != "table")) cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat); usedParagraph = true; if (localName == "p") { // text paragraph loadParagraph(tag, cursor); } else if (localName == "h") { // heading loadHeading(tag, cursor); } else if (localName == "unordered-list" || localName == "ordered-list" // OOo-1.1 || localName == "list" || localName == "numbered-paragraph") { // OASIS loadList(tag, cursor); } else if (localName == "section") { loadSection(tag, cursor); } else if (localName == "table-of-content") { loadTableOfContents(tag, cursor); } else if (localName == "bibliography") { loadBibliography(tag, cursor); } else { KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(tag, d->context); if (obj) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { KoVariableManager *varManager = textObjectManager->variableManager(); if (varManager) { textObjectManager->insertInlineObject(cursor, obj); } } } else { usedParagraph = false; warnText << "unhandled text:" << localName; } } } else if (tag.namespaceURI() == KoXmlNS::draw || tag.namespaceURI() == KoXmlNS::dr3d) { loadShape(tag, cursor); } else if (tag.namespaceURI() == KoXmlNS::table) { if (localName == "table") { loadTable(tag, cursor); usedParagraph = false; } else { warnText << "KoTextLoader::loadBody unhandled table::" << localName; } } processBody(); } } endBody(); } rootCallChecker--; // Here we put old endings after text insertion. if (mode == PasteMode) { QTextBlockFormat fmt = cursor.blockFormat(); oldSectionEndings = KoSectionUtils::sectionEndings(fmt); KoSectionUtils::setSectionEndings(fmt, oldSectionEndings); cursor.setBlockFormat(fmt); } cursor.endEditBlock(); KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); Q_UNUSED(textRangeManager); //debugText << "text ranges::"; //Q_FOREACH (KoTextRange *range, textRangeManager->textRanges()) { //debugText << range->id(); //} if (!rootCallChecker) { // Allow to move end bounds of sections with inserting text KoTextDocument(cursor.block().document()).sectionModel()->allowMovingEndBound(); } } void KoTextLoader::loadParagraph(const KoXmlElement &element, QTextCursor &cursor) { // TODO use the default style name a default value? const QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml); Q_ASSERT(d->styleManager); if (!paragraphStyle) { // Either the paragraph has no style or the style-name could not be found. // Fix up the paragraphStyle to be our default paragraph style in either case. if (!styleName.isEmpty()) warnText << "paragraph style " << styleName << "not found - using default style"; paragraphStyle = d->styleManager->defaultParagraphStyle(); } QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (paragraphStyle && (cursor.position() == cursor.block().position())) { QTextBlock block = cursor.block(); // Apply list style when loading a list but we don't have a list style paragraphStyle->applyStyle(block, d->currentLists[d->currentListLevel - 1] && !d->currentListStyle); // Clear the outline level property. If a default-outline-level was set, it should not // be applied when loading a document, only on user action. block.blockFormat().clearProperty(KoParagraphStyle::OutlineLevel); } // Some paragraph have id's defined which we need to store so that we can eg // attach text animations to this specific paragraph later on KoElementReference id; id.loadOdf(element); if (id.isValid() && d->shape) { QTextBlock block = cursor.block(); KoTextBlockData data(block); // this sets the user data, so don't remove d->context.addShapeSubItemId(d->shape, QVariant::fromValue(block.userData()), id.toString()); } // attach Rdf to cursor.block() // remember inline Rdf metadata -- if the xml-id is actually // about rdf. if (element.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { QTextBlock block = cursor.block(); KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)block.document(), block); if (inlineRdf->loadOdf(element)) { KoTextInlineRdf::attach(inlineRdf, cursor); } else { delete inlineRdf; inlineRdf = 0; } } #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()) << d->currentLists[d->currentListLevel - 1] << d->currentListStyle; #endif bool stripLeadingSpace = true; loadSpan(element, cursor, &stripLeadingSpace); QTextBlock block = cursor.block(); QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (d->endCharStyle) { QTextBlockFormat blockFormat = block.blockFormat(); blockFormat.setProperty(KoParagraphStyle::EndCharStyle, QVariant::fromValue< QSharedPointer >(QSharedPointer(d->endCharStyle->clone()))); cursor.setBlockFormat(blockFormat); } } d->endCharStyle = 0; cursor.setCharFormat(cf); // restore the cursor char format } void KoTextLoader::loadHeading(const KoXmlElement &element, QTextCursor &cursor) { Q_ASSERT(d->styleManager); int level = qMax(-1, element.attributeNS(KoXmlNS::text, "outline-level", "-1").toInt()); // This will fallback to the default-outline-level applied by KoParagraphStyle QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); QTextBlock block = cursor.block(); // Set the paragraph-style on the block KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml); if (!paragraphStyle) { paragraphStyle = d->styleManager->defaultParagraphStyle(); } if (paragraphStyle) { // Apply list style when loading a list but we don't have a list style paragraphStyle->applyStyle(block, (d->currentListLevel > 1) && d->currentLists[d->currentListLevel - 2] && !d->currentListStyle); } QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format bool stripLeadingSpace = true; loadSpan(element, cursor, &stripLeadingSpace); cursor.setCharFormat(cf); // restore the cursor char format if ((block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) && (level == -1)) { level = block.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); } else { if (level == -1) level = 1; QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::OutlineLevel, level); cursor.mergeBlockFormat(blockFormat); } if (element.hasAttributeNS(KoXmlNS::text, "is-list-header")) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::IsListHeader, element.attributeNS(KoXmlNS::text, "is-list-header") == "true"); cursor.mergeBlockFormat(blockFormat); } //we are defining our default behaviour here //Case 1: If text:outline-style is specified then we use the outline style to determine the numbering style //Case 2: If text:outline-style is not specified then if the element is inside a then it is numbered // otherwise it is not KoListStyle *outlineStyle = d->styleManager->outlineStyle(); if (!outlineStyle) { outlineStyle = d->styleManager->defaultOutlineStyle()->clone(); d->styleManager->setOutlineStyle(outlineStyle); } //if outline style is not specified and this is not inside a list then we do not number it if (outlineStyle->styleId() == d->styleManager->defaultOutlineStyle()->styleId()) { if (d->currentListLevel <= 1) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::UnnumberedListItem, true); cursor.mergeBlockFormat(blockFormat); } else { //inside a list then take the numbering from the list style int level = d->currentListLevel - 1; KoListLevelProperties llp; if (!d->currentListStyle->hasLevelProperties(level)) { // Look if one of the lower levels are defined to we can copy over that level. for(int i = level - 1; i >= 0; --i) { if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) { llp = d->currentLists[level - 1]->style()->levelProperties(i); break; } } } else { llp = d->currentListStyle->levelProperties(level); } llp.setLevel(level); outlineStyle->setLevelProperties(llp); } } KoList *list = KoTextDocument(block.document()).headingList(); if (!list) { list = d->list(block.document(), outlineStyle, false); KoTextDocument(block.document()).setHeadingList(list); } list->setStyle(outlineStyle); list->add(block, level); // attach Rdf to cursor.block() // remember inline Rdf metadata KoElementReference id; id.loadOdf(element); if (element.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { QTextBlock block = cursor.block(); KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)block.document(), block); if (inlineRdf->loadOdf(element)) { KoTextInlineRdf::attach(inlineRdf, cursor); } else { delete inlineRdf; inlineRdf = 0; } } #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif } void KoTextLoader::loadList(const KoXmlElement &element, QTextCursor &cursor) { const bool numberedParagraph = element.localName() == "numbered-paragraph"; QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); KoListStyle *listStyle = d->textSharedData->listStyle(styleName, d->stylesDotXml); KoList *continuedList = 0; int level; if (d->currentLists[d->currentListLevel - 1] || d->currentListLevel == 1) { d->currentLists[d->currentListLevel - 1] = 0; } else { d->currentLists[d->currentListLevel - 1] = d->currentLists[d->currentListLevel - 2]; } if (element.hasAttributeNS(KoXmlNS::text, "continue-list")) { if (d->xmlIdToListMap.contains(element.attributeNS(KoXmlNS::text, "continue-list", QString()))) { continuedList = d->xmlIdToListMap.value(element.attributeNS(KoXmlNS::text, "continue-list", QString())); } } else { //the ODF spec says that continue-numbering is considered only if continue-list is not specified if (element.hasAttributeNS(KoXmlNS::text, "continue-numbering")) { const QString continueNumbering = element.attributeNS(KoXmlNS::text, "continue-numbering", QString()); if (continueNumbering == "true") { //since ODF spec says "and the numbering style of the preceding list is the same as the current list" KoList *prevList = d->previousList(d->currentListLevel); if (prevList && listStyle && prevList->style()->hasLevelProperties(d->currentListLevel) && listStyle->hasLevelProperties(d->currentListLevel) && (prevList->style()->levelProperties(d->currentListLevel).style() == listStyle->levelProperties(d->currentListLevel).style())) { continuedList = prevList; } } } } // TODO: get level from the style, if it has a style:list-level attribute (new in ODF-1.2) if (numberedParagraph) { if (element.hasAttributeNS(KoXmlNS::text, "list-id")) { QString listId = element.attributeNS(KoXmlNS::text, "list-id"); if (d->numberedParagraphListId.contains(listId)) { d->currentLists.fill(d->numberedParagraphListId.value(listId)); } else { KoList *currentList = d->list(cursor.block().document(), listStyle, false); d->currentLists.fill(currentList); d->numberedParagraphListId.insert(listId, currentList); } } else { d->currentLists.fill(d->list(cursor.block().document(), listStyle, true)); } level = element.attributeNS(KoXmlNS::text, "level", "1").toInt(); d->currentListStyle = listStyle; } else { if (!listStyle) listStyle = d->currentListStyle; level = d->currentListLevel++; KoList *currentList = d->currentLists[d->currentListLevel - 2]; if (!currentList) { currentList = d->list(cursor.block().document(), listStyle, false); currentList->setListContinuedFrom(continuedList); d->currentLists[d->currentListLevel - 2] = currentList; } d->currentListStyle = listStyle; } if (element.hasAttributeNS(KoXmlNS::xml, "id")) { d->xmlIdToListMap.insert(element.attributeNS(KoXmlNS::xml, "id", QString()), d->currentLists[d->currentListLevel - 2]); } if (level < 0 || level > 10) { // should not happen but if it does then we should not crash/assert warnText << "Out of bounds list-level=" << level; level = qBound(0, level, 10); } if (! numberedParagraph) { d->setCurrentList(d->currentLists[d->currentListLevel - 2], level); } #ifdef KOOPENDOCUMENTLOADER_DEBUG if (d->currentListStyle) debugText << "styleName =" << styleName << "listStyle =" << d->currentListStyle->name() << "level =" << level << "hasLevelProperties =" << d->currentListStyle->hasLevelProperties(level) //<<" style="< childElementsList; for (KoXmlNode _node = element.firstChild(); !_node.isNull(); _node = _node.nextSibling()) { if (!(e = _node.toElement()).isNull()) { childElementsList.append(e); } } // Iterate over list items and add them to the textlist bool firstTime = true; foreach (e, childElementsList) { if (e.localName() != "removed-content") { if (!firstTime && !numberedParagraph) cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat); firstTime = false; loadListItem(e, cursor, level); } } if (numberedParagraph || --d->currentListLevel == 1) { d->currentListStyle = 0; d->currentLists.fill(0); } } void KoTextLoader::loadListItem(const KoXmlElement &e, QTextCursor &cursor, int level) { bool numberedParagraph = e.parentNode().toElement().localName() == "numbered-paragraph"; if (e.isNull() || e.namespaceURI() != KoXmlNS::text) return; const bool listHeader = e.tagName() == "list-header"; if (!numberedParagraph && e.tagName() != "list-item" && !listHeader) return; QTextBlock current = cursor.block(); QTextBlockFormat blockFormat; if (numberedParagraph) { if (e.localName() == "p") { loadParagraph(e, cursor); } else if (e.localName() == "h") { loadHeading(e, cursor); } blockFormat.setProperty(KoParagraphStyle::ListLevel, level); } else { loadBody(e, cursor); } if (!cursor.blockFormat().boolProperty(KoParagraphStyle::ForceDisablingList)) { if (!current.textList()) { if (!d->currentLists[level - 1]->style()->hasLevelProperties(level)) { KoListLevelProperties llp; // Look if one of the lower levels are defined to we can copy over that level. for(int i = level - 1; i >= 0; --i) { if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) { llp = d->currentLists[level - 1]->style()->levelProperties(i); break; } } llp.setLevel(level); // TODO make the 10 configurable llp.setIndent(level * 10.0); d->currentLists[level - 1]->style()->setLevelProperties(llp); } d->currentLists[level - 1]->add(current, level); } if (listHeader) blockFormat.setProperty(KoParagraphStyle::IsListHeader, true); if (e.hasAttributeNS(KoXmlNS::text, "start-value")) { int startValue = e.attributeNS(KoXmlNS::text, "start-value", QString()).toInt(); blockFormat.setProperty(KoParagraphStyle::ListStartValue, startValue); } // mark intermediate paragraphs as unnumbered items QTextCursor c(current); c.mergeBlockFormat(blockFormat); while (c.block() != cursor.block()) { c.movePosition(QTextCursor::NextBlock); if (c.block().textList()) // a sublist break; blockFormat = c.blockFormat(); blockFormat.setProperty(listHeader ? KoParagraphStyle::IsListHeader : KoParagraphStyle::UnnumberedListItem, true); c.setBlockFormat(blockFormat); d->currentLists[level - 1]->add(c.block(), level); } } debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); } void KoTextLoader::loadSection(const KoXmlElement §ionElem, QTextCursor &cursor) { KoSection *parent = d->sectionStack.empty() ? 0 : d->sectionStack.top(); KoSection *section = d->context.sectionModel()->createSection(cursor, parent); if (!section->loadOdf(sectionElem, d->textSharedData, d->stylesDotXml)) { delete section; warnText << "Could not load section"; return; } d->sectionStack << section; d->openingSections << section; loadBody(sectionElem, cursor); // Close the section on the last block of text we have loaded just now. QTextBlockFormat format = cursor.block().blockFormat(); KoSectionUtils::setSectionEndings(format, KoSectionUtils::sectionEndings(format) << d->context.sectionModel()->createSectionEnd(section)); d->sectionStack.pop(); cursor.setBlockFormat(format); section->setKeepEndBound(true); // This bound should stop moving with new text } void KoTextLoader::loadNote(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { QString className = noteElem.attributeNS(KoXmlNS::text, "note-class"); KoInlineNote *note = 0; int position = cursor.position(); // need to store this as the following might move is if (className == "footnote") { note = new KoInlineNote(KoInlineNote::Footnote); note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame()); } else { note = new KoInlineNote(KoInlineNote::Endnote); note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame()); } if (note->loadOdf(noteElem, d->context)) { cursor.setPosition(position); // restore the position before inserting the note textObjectManager->insertInlineObject(cursor, note); } else { cursor.setPosition(position); // restore the position delete note; } } } void KoTextLoader::loadCite(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { //Now creating citation with default type KoInlineCite::Citation. KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation); // the manager is needed during loading so set it now cite->setManager(textObjectManager); if (cite->loadOdf(noteElem, d->context)) { textObjectManager->insertInlineObject(cursor, cite); } else { delete cite; } } } void KoTextLoader::loadText(const QString &fulltext, QTextCursor &cursor, bool *stripLeadingSpace, bool isLastNode) { QString text = normalizeWhitespace(fulltext, *stripLeadingSpace); #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << " text=" << text << text.length() << *stripLeadingSpace; #endif if (!text.isEmpty()) { // if present text ends with a space, // we can remove the leading space in the next text *stripLeadingSpace = text[text.length() - 1].isSpace(); cursor.insertText(text); if (d->loadSpanLevel == 1 && isLastNode && cursor.position() > d->loadSpanInitialPos) { QTextCursor tempCursor(cursor); tempCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1); // select last char loaded if (tempCursor.selectedText() == " " && *stripLeadingSpace) { // if it's a collapsed blankspace tempCursor.removeSelectedText(); // remove it } } } } void KoTextLoader::loadSpan(const KoXmlElement &element, QTextCursor &cursor, bool *stripLeadingSpace) { #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif Q_ASSERT(stripLeadingSpace); if (d->loadSpanLevel++ == 0) d->loadSpanInitialPos = cursor.position(); for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { KoXmlElement ts = node.toElement(); const QString localName(ts.localName()); const bool isTextNS = ts.namespaceURI() == KoXmlNS::text; const bool isDrawNS = ts.namespaceURI() == KoXmlNS::draw; const bool isDr3dNS = ts.namespaceURI() == KoXmlNS::dr3d; const bool isOfficeNS = ts.namespaceURI() == KoXmlNS::office; //if (isOfficeNS) debugText << "office:"<endCharStyle = 0; } if (node.isText()) { bool isLastNode = node.nextSibling().isNull(); loadText(node.toText().data(), cursor, stripLeadingSpace, isLastNode); } else if (isTextNS && localName == "span") { // text:span #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << " localName=" << localName; #endif QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString()); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml); if (characterStyle) { characterStyle->applyStyle(&cursor); if (ts.firstChild().isNull()) { // empty span so let's save the characterStyle for possible use at end of par d->endCharStyle = characterStyle; } } else if (!styleName.isEmpty()) { warnText << "character style " << styleName << " not found"; } loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } else if (isTextNS && localName == "s") { // text:s int howmany = 1; if (ts.hasAttributeNS(KoXmlNS::text, "c")) { howmany = ts.attributeNS(KoXmlNS::text, "c", QString()).toInt(); } cursor.insertText(QString().fill(32, howmany)); *stripLeadingSpace = false; } else if ( (isTextNS && localName == "note")) { // text:note loadNote(ts, cursor); } else if (isTextNS && localName == "bibliography-mark") { // text:bibliography-mark loadCite(ts,cursor); } else if (isTextNS && localName == "tab") { // text:tab cursor.insertText("\t"); *stripLeadingSpace = false; } else if (isTextNS && localName == "a") { // text:a QString target = ts.attributeNS(KoXmlNS::xlink, "href"); QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString()); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (!styleName.isEmpty()) { KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml); if (characterStyle) { characterStyle->applyStyle(&cursor); } else { warnText << "character style " << styleName << " not found"; } } QTextCharFormat newCharFormat = cursor.charFormat(); newCharFormat.setAnchor(true); newCharFormat.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); newCharFormat.setAnchorHref(target); cursor.setCharFormat(newCharFormat); loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } else if (isTextNS && localName == "line-break") { // text:line-break #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << " Node localName=" << localName; #endif cursor.insertText(QChar(0x2028)); *stripLeadingSpace = false; } else if (isTextNS && localName == "soft-page-break") { // text:soft-page-break KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { textObjectManager->insertInlineObject(cursor, new KoTextSoftPageBreak()); } } else if (isTextNS && localName == "meta") { #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "loading a text:meta"; #endif KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { const QTextDocument *document = cursor.block().document(); KoTextMeta* startmark = new KoTextMeta(document); textObjectManager->insertInlineObject(cursor, startmark); // Add inline Rdf here. KoElementReference id; id.loadOdf(ts); if (ts.hasAttributeNS(KoXmlNS::xhtml, "property") || (id.isValid() && d->rdfIdList.contains(id.toString()))) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)document, startmark); if (inlineRdf->loadOdf(ts)) { startmark->setInlineRdf(inlineRdf); } else { delete inlineRdf; inlineRdf = 0; } } loadSpan(ts, cursor, stripLeadingSpace); // recurse KoTextMeta* endmark = new KoTextMeta(document); textObjectManager->insertInlineObject(cursor, endmark); startmark->setEndBookmark(endmark); } } // text:bookmark, text:bookmark-start and text:bookmark-end else if (isTextNS && (localName == "bookmark" || localName == "bookmark-start" || localName == "bookmark-end")) { KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); if (localName == "bookmark-end") { KoBookmark *bookmark = textRangeManager->bookmarkManager()->bookmark(KoBookmark::createUniqueBookmarkName(textRangeManager->bookmarkManager(), ts.attribute("name"), true)); if (bookmark) { bookmark->setRangeEnd(cursor.position()); } } else { KoBookmark *bookmark = new KoBookmark(cursor); bookmark->setManager(textRangeManager); if (textRangeManager && bookmark->loadOdf(ts, d->context)) { textRangeManager->insert(bookmark); } else { warnText << "Could not load bookmark"; delete bookmark; } } } else if (isTextNS && localName == "bookmark-ref") { QString bookmarkName = ts.attribute("ref-name"); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (!bookmarkName.isEmpty()) { QTextCharFormat linkCf(cf); // and copy it to alter it linkCf.setAnchor(true); linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Bookmark); QStringList anchorName; anchorName << bookmarkName; linkCf.setAnchorHref('#'+ bookmarkName); cursor.setCharFormat(linkCf); } // TODO add support for loading text:reference-format loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } // text:annotation and text:annotation-end else if (isOfficeNS && (localName == "annotation" || localName == "annotation-end")) { debugText << "------> annotation found" << localName; KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); if (localName == "annotation-end") { // If the tag is annotation-end, there should already be a KoAnnotation // with the same name as this one. If so, just find it and set the end // of the range to the current position. KoAnnotation *annotation = textRangeManager->annotationManager()->annotation(KoAnnotation::createUniqueAnnotationName(textRangeManager->annotationManager(), ts.attribute("name"), true)); if (annotation) { annotation->setRangeEnd(cursor.position()); } } else { // if the tag is "annotation" then create a KoAnnotation // and an annotation shape and call loadOdf() in them. KoAnnotation *annotation = new KoAnnotation(cursor); annotation->setManager(textRangeManager); if (textRangeManager && annotation->loadOdf(ts, d->context)) { textRangeManager->insert(annotation); KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(ts, d->context); // Don't show it before it's being laid out. // // Also this hides it in all applications but // those that have an annotation layout manager // (currently: words, author). shape->setVisible(false); d->textSharedData->shapeInserted(shape, element, d->context); annotation->setAnnotationShape(shape); } else { warnText << "Could not load annotation"; delete annotation; } } } else if (isTextNS && localName == "number") { // text:number /* ODF Spec, §4.1.1, Formatted Heading Numbering If a heading has a numbering applied, the text of the formatted number can be included in a element. This text can be used by applications that do not support numbering of headings, but it will be ignored by applications that support numbering. */ } else if (isTextNS && localName == "dde-connection") { // TODO: load actual connection (or at least preserve it) // For now: just load the text for (KoXmlNode n = ts.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isText()) { loadText(n.toText().data(), cursor, stripLeadingSpace, false); } } } else if ((isDrawNS) && localName == "a") { // draw:a loadShapeWithHyperLink(ts, cursor); } else if (isDrawNS || isDr3dNS) { loadShape(ts, cursor); } else { KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(ts, d->context); KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (obj && textObjectManager) { KoVariableManager *varManager = textObjectManager->variableManager(); if (varManager) { textObjectManager->insertInlineObject(cursor, obj); // we need to update whitespace stripping here so we don't remove to many whitespaces. // this is simplified as it assumes the first child it the text item but that should be the case // most of the time with variables so it should be fine. KoXmlNode child = ts.firstChild(); if (child.isText()) { QString text = normalizeWhitespace(child.toText().data(), *stripLeadingSpace); if (!text.isEmpty()) { // if present text ends with a space, // we can remove the leading space in the next text *stripLeadingSpace = text[text.length() - 1].isSpace(); } } } } else { #if 0 //1.6: bool handled = false; // Check if it's a variable KoVariable *var = context.variableCollection().loadOasisField(textDocument(), ts, context); if (var) { textData = "#"; // field placeholder customItem = var; handled = true; } if (!handled) { handled = textDocument()->loadSpanTag(ts, context, this, pos, textData, customItem); if (!handled) { warnText << "Ignoring tag " << ts.tagName(); context.styleStack().restore(); continue; } } #else #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "Node '" << localName << "' unhandled"; #endif } #endif } } --d->loadSpanLevel; } void KoTextLoader::loadTable(const KoXmlElement &tableElem, QTextCursor &cursor) { QTextTableFormat tableFormat; QString tableStyleName = tableElem.attributeNS(KoXmlNS::table, "style-name", ""); if (!tableStyleName.isEmpty()) { KoTableStyle *tblStyle = d->textSharedData->tableStyle(tableStyleName, d->stylesDotXml); if (tblStyle) tblStyle->applyStyle(tableFormat); } QString tableTemplateName = tableElem.attributeNS(KoXmlNS::table, "template-name", ""); if (! tableTemplateName.isEmpty()) { if(KoTextTableTemplate *tableTemplate = d->styleManager->tableTemplate(tableTemplateName)) { tableFormat.setProperty(KoTableStyle::TableTemplate, tableTemplate->styleId()); } } if (tableElem.attributeNS(KoXmlNS::table, "use-banding-columns-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseBandingColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-banding-rows-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseBandingRowStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-first-column-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseFirstColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-first-row-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseFirstRowStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-last-column-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseLastColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-last-row-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseLastRowStyles, true); } // Let's try to figure out when to hide the current block QTextBlock currentBlock = cursor.block(); QTextTable *outerTable = cursor.currentTable(); bool hide = cursor.position() == 0; if (outerTable) { QTextTableCell cell = outerTable->cellAt(cursor.position()); if (cursor.position() == cell.firstCursorPosition().position()) { hide = true; } } if (!hide) { // Let's insert an extra block so that will be the one we hide instead cursor.insertBlock(cursor.blockFormat(), cursor.blockCharFormat()); currentBlock = cursor.block(); } if (tableElem.attributeNS(KoXmlNS::table, "protected", "false") == "true") { tableFormat.setProperty(KoTableStyle::TableIsProtected, true); } QTextTable *tbl = cursor.insertTable(1, 1, tableFormat); // 'Hide' the block before the table (possibly the extra block we just inserted) QTextBlockFormat blockFormat; //blockFormat.setFont(currentBlock.blockFormat().font()); QTextCursor tmpCursor(currentBlock); blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true); QVariant masterStyle = tableFormat.property(KoTableStyle::MasterPageName); if (!masterStyle.isNull()) { // if table has a master page style property, copy it to block before table, because // this block is a hidden block so let's make it belong to the same masterpage // so we don't end up with a page break at the wrong place blockFormat.setProperty(KoParagraphStyle::MasterPageName, masterStyle); } tmpCursor.setBlockFormat(blockFormat); KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int rows = 0; int columns = 0; QList spanStore; //temporary list to store spans until the entire table have been created KoXmlElement tblTag; int headingRowCounter = 0; QList rowTags; forEachElement(tblTag, tableElem) { if (! tblTag.isNull()) { const QString tblLocalName = tblTag.localName(); if (tblTag.namespaceURI() == KoXmlNS::table) { if (tblLocalName == "table-column") { loadTableColumn(tblTag, tbl, columns); } else if (tblLocalName == "table-columns") { KoXmlElement e; forEachElement(e, tblTag) { if (e.localName() == "table-column") { loadTableColumn(e, tbl, columns); } } } else if (tblLocalName == "table-row") { loadTableRow(tblTag, tbl, spanStore, cursor, rows); } else if (tblLocalName == "table-rows") { KoXmlElement subTag; forEachElement(subTag, tblTag) { if (!subTag.isNull()) { if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) { loadTableRow(subTag, tbl, spanStore, cursor, rows); } } } } else if (tblLocalName == "table-header-rows") { KoXmlElement subTag; forEachElement(subTag, tblTag) { if (!subTag.isNull()) { if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) { headingRowCounter++; loadTableRow(subTag, tbl, spanStore, cursor, rows); } } } } } } } if (headingRowCounter > 0) { QTextTableFormat fmt = tbl->format(); fmt.setProperty(KoTableStyle::NumberHeadingRows, headingRowCounter); tbl->setFormat(fmt); } // Finally create spans Q_FOREACH (const QRect &span, spanStore) { tbl->mergeCells(span.y(), span.x(), span.height(), span.width()); // for some reason Qt takes row, column } cursor = tbl->lastCursorPosition(); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); } void KoTextLoader::loadTableColumn(const KoXmlElement &tblTag, QTextTable *tbl, int &columns) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int rows = tbl->rows(); int repeatColumn = tblTag.attributeNS(KoXmlNS::table, "number-columns-repeated", "1").toInt(); QString columnStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", ""); if (!columnStyleName.isEmpty()) { KoTableColumnStyle *columnStyle = d->textSharedData->tableColumnStyle(columnStyleName, d->stylesDotXml); if (columnStyle) { for (int c = columns; c < columns + repeatColumn; c++) { tcarManager.setColumnStyle(c, *columnStyle); } } } QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", ""); if (!defaultCellStyleName.isEmpty()) { KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml); for (int c = columns; c < columns + repeatColumn; c++) { tcarManager.setDefaultColumnCellStyle(c, cellStyle); } } columns = columns + repeatColumn; if (rows > 0) tbl->resize(rows, columns); else tbl->resize(1, columns); } void KoTextLoader::loadTableRow(const KoXmlElement &tblTag, QTextTable *tbl, QList &spanStore, QTextCursor &cursor, int &rows) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int columns = tbl->columns(); QString rowStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", ""); if (!rowStyleName.isEmpty()) { KoTableRowStyle *rowStyle = d->textSharedData->tableRowStyle(rowStyleName, d->stylesDotXml); if (rowStyle) { tcarManager.setRowStyle(rows, *rowStyle); } } QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", ""); if (!defaultCellStyleName.isEmpty()) { KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml); tcarManager.setDefaultRowCellStyle(rows, cellStyle); } rows++; if (columns > 0) tbl->resize(rows, columns); else tbl->resize(rows, 1); // Added a row int currentCell = 0; KoXmlElement rowTag; forEachElement(rowTag, tblTag) { if (!rowTag.isNull()) { const QString rowLocalName = rowTag.localName(); if (rowTag.namespaceURI() == KoXmlNS::table) { if (rowLocalName == "table-cell") { loadTableCell(rowTag, tbl, spanStore, cursor, currentCell); currentCell++; } else if (rowLocalName == "covered-table-cell") { currentCell++; } } } } } void KoTextLoader::loadTableCell(const KoXmlElement &rowTag, QTextTable *tbl, QList &spanStore, QTextCursor &cursor, int ¤tCell) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); const int currentRow = tbl->rows() - 1; QTextTableCell cell = tbl->cellAt(currentRow, currentCell); // store spans until entire table have been loaded int rowsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-rows-spanned", "1").toInt(); int columnsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-columns-spanned", "1").toInt(); spanStore.append(QRect(currentCell, currentRow, columnsSpanned, rowsSpanned)); if (cell.isValid()) { QString cellStyleName = rowTag.attributeNS(KoXmlNS::table, "style-name", ""); KoTableCellStyle *cellStyle = 0; if (!cellStyleName.isEmpty()) { cellStyle = d->textSharedData->tableCellStyle(cellStyleName, d->stylesDotXml); } else if (tcarManager.defaultRowCellStyle(currentRow)) { cellStyle = tcarManager.defaultRowCellStyle(currentRow); } else if (tcarManager.defaultColumnCellStyle(currentCell)) { cellStyle = tcarManager.defaultColumnCellStyle(currentCell); } if (cellStyle) cellStyle->applyStyle(cell); QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); if (rowTag.attributeNS(KoXmlNS::table, "protected", "false") == "true") { cellFormat.setProperty(KoTableCellStyle::CellIsProtected, true); } cell.setFormat(cellFormat); // handle inline Rdf // rowTag is the current table cell. KoElementReference id; id.loadOdf(rowTag); if (rowTag.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)cursor.block().document(),cell); if (inlineRdf->loadOdf(rowTag)) { QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); cellFormat.setProperty(KoTableCellStyle::InlineRdf,QVariant::fromValue(inlineRdf)); cell.setFormat(cellFormat); } else { delete inlineRdf; inlineRdf = 0; } } cursor = cell.firstCursorPosition(); loadBody(rowTag, cursor); } } void KoTextLoader::loadShapeWithHyperLink(const KoXmlElement &element, QTextCursor& cursor) { // get the hyperlink QString hyperLink = element.attributeNS(KoXmlNS::xlink, "href"); KoShape *shape = 0; //load the shape for hyperlink KoXmlNode node = element.firstChild(); if (!node.isNull()) { KoXmlElement ts = node.toElement(); shape = loadShape(ts, cursor); if (shape) { shape->setHyperLink(hyperLink); } } } KoShape *KoTextLoader::loadShape(const KoXmlElement &element, QTextCursor &cursor) { KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(element, d->context); if (!shape) { debugText << "shape '" << element.localName() << "' unhandled"; return 0; } KoShapeAnchor *anchor = new KoShapeAnchor(shape); anchor->loadOdf(element, d->context); // shape->setAnchor(anchor); // undefined in Krita! d->textSharedData->shapeInserted(shape, element, d->context); // page anchored shapes are handled differently if (anchor->anchorType() == KoShapeAnchor::AnchorPage) { // nothing else to do + delete anchor; } else if (anchor->anchorType() == KoShapeAnchor::AnchorAsCharacter) { KoAnchorInlineObject *anchorObject = new KoAnchorInlineObject(anchor); KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { textObjectManager->insertInlineObject(cursor, anchorObject); } } else { // KoShapeAnchor::AnchorToCharacter or KoShapeAnchor::AnchorParagraph KoAnchorTextRange *anchorRange = new KoAnchorTextRange(anchor, cursor); KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); anchorRange->setManager(textRangeManager); textRangeManager->insert(anchorRange); } return shape; } -void KoTextLoader::loadTableOfContents(const KoXmlElement &element, QTextCursor &cursor) +void KoTextLoader::loadTableOfContents(const KoXmlElement &/*element*/, QTextCursor &/*cursor*/) { - // make sure that the tag is table-of-content - Q_ASSERT(element.tagName() == "table-of-content"); - QTextBlockFormat tocFormat; - - // for "meta-information" about the TOC we use this class - KoTableOfContentsGeneratorInfo *info = new KoTableOfContentsGeneratorInfo(); - - // to store the contents we use an extrafor "meta-information" about the TOC we use this class - QTextDocument *tocDocument = new QTextDocument(); - KoTextDocument(tocDocument).setStyleManager(d->styleManager); - KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager); - - info->m_name = element.attribute("name"); - info->m_styleName = element.attribute("style-name"); - - KoXmlElement e; - forEachElement(e, element) { - if (e.isNull() || e.namespaceURI() != KoXmlNS::text) { - continue; - } - - if (e.localName() == "table-of-content-source" && e.namespaceURI() == KoXmlNS::text) { - info->loadOdf(d->textSharedData, e); - // uncomment to see what has been loaded - //info.tableOfContentData()->dump(); - tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(info) ); - tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(tocDocument) ); - cursor.insertBlock(tocFormat); - - // We'll just try to find displayable elements and add them as paragraphs - } else if (e.localName() == "index-body") { - QTextCursor cursorFrame = tocDocument->rootFrame()->lastCursorPosition(); - - bool firstTime = true; - KoXmlElement p; - forEachElement(p, e) { - // All elem will be "p" instead of the title, which is particular - if (p.isNull() || p.namespaceURI() != KoXmlNS::text) - continue; - - if (!firstTime) { - // use empty formats to not inherit from the prev parag - QTextBlockFormat bf; - QTextCharFormat cf; - cursorFrame.insertBlock(bf, cf); - } - firstTime = false; - - QTextBlock current = cursorFrame.block(); - QTextBlockFormat blockFormat; - - if (p.localName() == "p") { - loadParagraph(p, cursorFrame); - } else if (p.localName() == "index-title") { - loadBody(p, cursorFrame); - } - - QTextCursor c(current); - c.mergeBlockFormat(blockFormat); - } - - }// index-body - } } -void KoTextLoader::loadBibliography(const KoXmlElement &element, QTextCursor &cursor) +void KoTextLoader::loadBibliography(const KoXmlElement &/*element*/, QTextCursor &/*cursor*/) { - // make sure that the tag is bibliography - Q_ASSERT(element.tagName() == "bibliography"); - QTextBlockFormat bibFormat; - - // for "meta-information" about the bibliography we use this class - KoBibliographyInfo *info = new KoBibliographyInfo(); - - QTextDocument *bibDocument = new QTextDocument(); - KoTextDocument(bibDocument).setStyleManager(d->styleManager); - KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager); - - info->m_name = element.attribute("name"); - info->m_styleName = element.attribute("style-name"); - - KoXmlElement e; - forEachElement(e, element) { - if (e.isNull() || e.namespaceURI() != KoXmlNS::text) { - continue; - } - - if (e.localName() == "bibliography-source" && e.namespaceURI() == KoXmlNS::text) { - info->loadOdf(d->textSharedData, e); - - bibFormat.setProperty(KoParagraphStyle::BibliographyData, QVariant::fromValue(info)); - bibFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(bibDocument)); - - cursor.insertBlock(bibFormat); - // We'll just try to find displayable elements and add them as paragraphs - } else if (e.localName() == "index-body") { - QTextCursor cursorFrame = bibDocument->rootFrame()->lastCursorPosition(); - - bool firstTime = true; - KoXmlElement p; - forEachElement(p, e) { - // All elem will be "p" instead of the title, which is particular - if (p.isNull() || p.namespaceURI() != KoXmlNS::text) - continue; - - if (!firstTime) { - // use empty formats to not inherit from the prev parag - QTextBlockFormat bf; - QTextCharFormat cf; - cursorFrame.insertBlock(bf, cf); - } - firstTime = false; - - QTextBlock current = cursorFrame.block(); - QTextBlockFormat blockFormat; - - if (p.localName() == "p") { - loadParagraph(p, cursorFrame); - } else if (p.localName() == "index-title") { - loadBody(p, cursorFrame); - } - - QTextCursor c(current); - c.mergeBlockFormat(blockFormat); - } - - }// index-body - } } void KoTextLoader::startBody(int total) { d->bodyProgressTotal += total; } void KoTextLoader::processBody() { d->bodyProgressValue++; if (d->progressTime.elapsed() >= d->nextProgressReportMs) { // update based on elapsed time, don't saturate the queue d->nextProgressReportMs = d->progressTime.elapsed() + 333; // report 3 times per second Q_ASSERT(d->bodyProgressTotal > 0); const int percent = d->bodyProgressValue * 100 / d->bodyProgressTotal; emit sigProgress(percent); } } void KoTextLoader::endBody() { } diff --git a/plugins/flake/textshape/textlayout/KoPointedAt.h b/plugins/flake/textshape/textlayout/KoPointedAt.h index 01f09beaf0..27f7c53997 100644 --- a/plugins/flake/textshape/textlayout/KoPointedAt.h +++ b/plugins/flake/textshape/textlayout/KoPointedAt.h @@ -1,63 +1,63 @@ /* This file is part of the KDE project * Copyright (C) 2011 C. Boemann, KO GmbH * Copyright (C) 2011 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. */ #ifndef KOPOINTEDAT_H #define KOPOINTEDAT_H #include "kritatextlayout_export.h" #include #include #include class KoBookmark; class QTextTable; class KoInlineTextObjectManager; class KoTextRangeManager; class KoInlineNote; class KRITATEXTLAYOUT_EXPORT KoPointedAt { public: KoPointedAt(); explicit KoPointedAt(KoPointedAt *other); void fillInLinks(const QTextCursor &cursor, KoInlineTextObjectManager *inlineManager, KoTextRangeManager *rangeManager); enum TableHit { None , ColumnDivider , RowDivider }; int position; - KoBookmark *bookmark; + KoBookmark *bookmark {0}; QString externalHRef; - KoInlineNote *note; - int noteReference; - QTextTable *table; - TableHit tableHit; - int tableRowDivider; - int tableColumnDivider; - qreal tableLeadSize; - qreal tableTrailSize; + KoInlineNote *note {0}; + int noteReference {0}; + QTextTable *table {0}; + TableHit tableHit {None}; + int tableRowDivider {0}; + int tableColumnDivider {0}; + qreal tableLeadSize {0.0}; + qreal tableTrailSize {0.0}; QPointF tableDividerPos; }; #endif diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp index 4ae0d6544b..3ca5011b3f 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp @@ -1,581 +1,583 @@ /** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2006-12-09 * @brief a tread-safe libraw C++ program interface * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * 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, 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. * * ============================================================ */ #include "kdcraw.h" #include "kdcraw_p.h" // Qt includes #include #include #include // KDE includes //#include // LibRaw includes #include #ifdef LIBRAW_HAS_CONFIG #include #endif // Local includes #include "libkdcraw_debug.h" #include "libkdcraw_version.h" #include "rawfiles.h" namespace KDcrawIface { KDcraw::KDcraw() : d(new Private(this)) { m_cancel = false; } KDcraw::~KDcraw() { cancel(); delete d; } QString KDcraw::version() { return QString(KDCRAW_VERSION_STRING); } void KDcraw::cancel() { m_cancel = true; } bool KDcraw::loadRawPreview(QImage& image, const QString& path) { // In first, try to extract the embedded JPEG preview. Very fast. bool ret = loadEmbeddedPreview(image, path); if (ret) return true; // In second, decode and half size of RAW picture. More slow. return (loadHalfPreview(image, path)); } bool KDcraw::loadEmbeddedPreview(QImage& image, const QString& path) { QByteArray imgData; if ( loadEmbeddedPreview(imgData, path) ) { qCDebug(LIBKDCRAW_LOG) << "Preview data size:" << imgData.size(); if (image.loadFromData( imgData )) { qCDebug(LIBKDCRAW_LOG) << "Using embedded RAW preview extraction"; return true; } } qCDebug(LIBKDCRAW_LOG) << "Failed to load embedded RAW preview"; return false; } bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } return (Private::loadEmbeddedPreview(imgData, raw)); } bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QBuffer& buffer) { QString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); LibRaw raw; QByteArray inData = buffer.data(); int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_buffer: " << libraw_strerror(ret); raw.recycle(); return false; } return (Private::loadEmbeddedPreview(imgData, raw)); } bool KDcraw::loadHalfPreview(QImage& image, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction"; LibRaw raw; raw.imgdata.params.use_auto_wb = 1; // Use automatic white balance. raw.imgdata.params.use_camera_wb = 1; // Use camera white balance, if possible. raw.imgdata.params.half_size = 1; // Half-size color image (3x faster than -q). int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if(!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "Failed to get half preview from LibRaw!"; return false; } qCDebug(LIBKDCRAW_LOG) << "Using reduced RAW picture extraction"; return true; } bool KDcraw::loadHalfPreview(QByteArray& imgData, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction"; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } QImage image; if (!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret); return false; } QBuffer buffer(&imgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPEG"); return true; } bool KDcraw::loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer) { QString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); LibRaw raw; QByteArray inData = inBuffer.data(); int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } QImage image; if (!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret); return false; } QBuffer buffer(&imgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPG"); return true; } bool KDcraw::loadFullImage(QImage& image, const QString& path, const RawDecodingSettings& settings) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to load full RAW picture..."; RawDecodingSettings prm = settings; prm.sixteenBitsImage = false; QByteArray imgData; int width, height, rgbmax; KDcraw decoder; bool ret = decoder.decodeRAWImage(path, prm, imgData, width, height, rgbmax); if (!ret) { qCDebug(LIBKDCRAW_LOG) << "Failled to load full RAW picture"; return false; } uchar* sptr = (uchar*)imgData.data(); uchar tmp8[2]; // Set RGB color components. for (int i = 0 ; i < width * height ; ++i) { // Swap Red and Blue tmp8[0] = sptr[2]; tmp8[1] = sptr[0]; sptr[0] = tmp8[0]; sptr[2] = tmp8[1]; sptr += 3; } image = QImage(width, height, QImage::Format_ARGB32); uint* dptr = reinterpret_cast(image.bits()); sptr = (uchar*)imgData.data(); for (int i = 0 ; i < width * height ; ++i) { *dptr++ = qRgba(sptr[2], sptr[1], sptr[0], 0xFF); sptr += 3; } qCDebug(LIBKDCRAW_LOG) << "Load full RAW picture done"; return true; } bool KDcraw::rawFileIdentify(DcrawInfoContainer& identify, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); identify.isDecodable = false; if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } ret = raw.adjust_sizes_info_only(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run adjust_sizes_info_only: " << libraw_strerror(ret); raw.recycle(); return false; } Private::fillIndentifyInfo(&raw, identify); raw.recycle(); return true; } // ---------------------------------------------------------------------------------- bool KDcraw::extractRAWData(const QString& filePath, QByteArray& rawData, DcrawInfoContainer& identify, unsigned int shotSelect) { QFileInfo fileInfo(filePath); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); identify.isDecodable = false; if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; if (m_cancel) return false; d->setProgress(0.1); LibRaw raw; // Set progress call back function. raw.set_progress_handler(callbackForLibRaw, d); int ret = raw.open_file((const char*)(QFile::encodeName(filePath)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.3); raw.imgdata.params.output_bps = 16; raw.imgdata.params.shot_select = shotSelect; ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.4); ret = raw.raw2image(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run raw2image: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.6); Private::fillIndentifyInfo(&raw, identify); if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.8); rawData = QByteArray(); if (raw.imgdata.idata.filters == 0) { rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * raw.imgdata.idata.colors * sizeof(unsigned short))); unsigned short* output = reinterpret_cast(rawData.data()); for (unsigned int row = 0; row < raw.imgdata.sizes.iheight; row++) { for (unsigned int col = 0; col < raw.imgdata.sizes.iwidth; col++) { for (int color = 0; color < raw.imgdata.idata.colors; color++) { *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][color]; output++; } } } } else { - rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * sizeof(unsigned short))); + int w = raw.imgdata.sizes.iwidth; + int h = raw.imgdata.sizes.iheight; + rawData.resize(w * h * sizeof(unsigned short)); unsigned short* output = reinterpret_cast(rawData.data()); for (uint row = 0; row < raw.imgdata.sizes.iheight; row++) { for (uint col = 0; col < raw.imgdata.sizes.iwidth; col++) { *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][raw.COLOR(row, col)]; output++; } } } raw.recycle(); d->setProgress(1.0); return true; } bool KDcraw::decodeHalfRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_rawDecodingSettings = rawDecodingSettings; m_rawDecodingSettings.halfSizeColorImage = true; return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax)); } bool KDcraw::decodeRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_rawDecodingSettings = rawDecodingSettings; return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax)); } bool KDcraw::checkToCancelWaitingData() { return m_cancel; } void KDcraw::setWaitingDataProgress(double) { } const char* KDcraw::rawFiles() { return raw_file_extentions; } QStringList KDcraw::rawFilesList() { QString string = QString::fromLatin1(rawFiles()); return string.remove("*.").split(' '); } int KDcraw::rawFilesVersion() { return raw_file_extensions_version; } QStringList KDcraw::supportedCamera() { QStringList camera; const char** const list = LibRaw::cameraList(); for (int i = 0; i < LibRaw::cameraCount(); i++) camera.append(list[i]); return camera; } QString KDcraw::librawVersion() { return QString(LIBRAW_VERSION_STR).remove("-Release"); } int KDcraw::librawUseGomp() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_OPENMP return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseRawSpeed() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_RAWSPEED return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseGPL2DemosaicPack() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL2 return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseGPL3DemosaicPack() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL3 return true; # else return false; # endif #else return -1; #endif } } // namespace KDcrawIface diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.c b/plugins/impex/xcf/3rdparty/xcftools/flatten.c index bfce8bf099..fb10ca7c5d 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatten.c +++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.c @@ -1,725 +1,727 @@ /* Flattning functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include "flatten.h" #include "pixels.h" #include #include #include static rgba __ATTRIBUTE__((noinline,const)) composite_one(rgba bot,rgba top) { unsigned tfrac, alpha ; tfrac = ALPHA(top) ; alpha = 255 ; if( !FULLALPHA(bot) ) { alpha = 255 ^ scaletable[255-ALPHA(bot)][255-ALPHA(top)] ; /* This peculiar combination of ^ and - makes the GCC code * generator for i386 particularly happy. */ tfrac = (256*ALPHA(top) - 1) / alpha ; /* Tfrac is the fraction of the coposited pixel's covered area * that comes from the top pixel. * For mathematical accuracy we ought to scale by 255 and * subtract alpha/2, but this is faster, and never misses the * true value by more than one 1/255. This effect is completely * overshadowed by the linear interpolation in the first place. * (I.e. gamma is ignored when combining intensities). * [In any case, complete fairness is not possible: if the * bottom pixel had alpha=170 and the top has alpha=102, * each should contribute equally to the color of the * resulting alpha=204 pixel, which is not possible in general] * Subtracting one helps the topfrac never be 256, which would * be bad. * On the other hand it means that we would get tfrac=-1 if the * top pixel is completely transparent, and we get a division * by zero if _both_ pixels are fully transparent. These cases * must be handled by all callers. * More snooping in the Gimp sources reveal that it uses * floating-point for its equivalent of tfrac when the * bottom layer has an alpha channel. (alphify() macro * in paint-funcs.c). What gives? */ } return (alpha << ALPHA_SHIFT) + ((uint32_t)scaletable[ tfrac ][255&(top>>RED_SHIFT )] << RED_SHIFT ) + ((uint32_t)scaletable[ tfrac ][255&(top>>GREEN_SHIFT)] << GREEN_SHIFT ) + ((uint32_t)scaletable[ tfrac ][255&(top>>BLUE_SHIFT )] << BLUE_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>RED_SHIFT )] << RED_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>GREEN_SHIFT)] << GREEN_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>BLUE_SHIFT )] << BLUE_SHIFT ) ; } /* merge_normal() takes ownership of bot. * merge_normal() will share ownership of top. * Return: may be shared. */ static struct Tile * __ATTRIBUTE__((noinline)) merge_normal(struct Tile *bot, struct Tile *top) { unsigned i ; assertTileCompatibility(bot,top); /* See if there is an easy winner */ if( (bot->summary & TILESUMMARY_ALLNULL) || (top->summary & TILESUMMARY_ALLFULL) ) { freeTile(bot); return top ; } if( top->summary & TILESUMMARY_ALLNULL ) { freeTile(top); return bot ; } /* Try hard to make top win */ for( i=0; ; i++ ) { if( i == top->count ) { freeTile(bot); return top ; } if( !(NULLALPHA(bot->pixels[i]) || FULLALPHA(top->pixels[i])) ) break ; } INIT_SCALETABLE_IF( !(top->summary & TILESUMMARY_CRISP) ); /* Otherwise bot wins, but is forever changed ... */ if( (top->summary & TILESUMMARY_ALLNULL) == 0 ) { unsigned i ; invalidateSummary(bot,0); for( i=0 ; i < top->count ; i++ ) { if( !NULLALPHA(top->pixels[i]) ) { if( FULLALPHA(top->pixels[i]) || NULLALPHA(bot->pixels[i]) ) bot->pixels[i] = top->pixels[i] ; else bot->pixels[i] = composite_one(bot->pixels[i],top->pixels[i]); } } } freeTile(top); return bot ; } #define exotic_combinator static unsigned __ATTRIBUTE__((const)) exotic_combinator ucombine_ADDITION(uint8_t bot,uint8_t top) { return bot+top > 255 ? 255 : bot+top ; } exotic_combinator ucombine_SUBTRACT(uint8_t bot,uint8_t top) { return top>bot ? 0 : bot-top ; } exotic_combinator ucombine_LIGHTEN_ONLY(uint8_t bot,uint8_t top) { return top > bot ? top : bot ; } exotic_combinator ucombine_DARKEN_ONLY(uint8_t bot,uint8_t top) { return top < bot ? top : bot ; } exotic_combinator ucombine_DIFFERENCE(uint8_t bot,uint8_t top) { return top > bot ? top-bot : bot-top ; } exotic_combinator ucombine_MULTIPLY(uint8_t bot,uint8_t top) { return scaletable[bot][top] ; } exotic_combinator ucombine_DIVIDE(uint8_t bot,uint8_t top) { int result = (int)bot*256 / (1+top) ; return result >= 256 ? 255 : result ; } exotic_combinator ucombine_SCREEN(uint8_t bot,uint8_t top) { /* An inverted version of "multiply" */ return 255 ^ scaletable[255-bot][255-top] ; } exotic_combinator ucombine_OVERLAY(uint8_t bot,uint8_t top) { return scaletable[bot][bot] + 2*scaletable[top][scaletable[bot][255-bot]] ; /* This strange formula is equivalent to * (1-top)*(bot^2) + top*(1-(1-top)^2) * that is, the top value is used to interpolate between * the self-multiply and the self-screen of the bottom. */ /* Note: This is exactly what the "Soft light" effect also * does, though with different code in the Gimp. */ } exotic_combinator ucombine_DODGE(uint8_t bot,uint8_t top) { return ucombine_DIVIDE(bot,255-top); } exotic_combinator ucombine_BURN(uint8_t bot,uint8_t top) { return 255 - ucombine_DIVIDE(255-bot,top); } exotic_combinator ucombine_HARDLIGHT(uint8_t bot,uint8_t top) { if( top >= 128 ) return 255 ^ scaletable[255-bot][2*(255-top)] ; else return scaletable[bot][2*top]; /* The code that implements "hardlight" in Gimp 2.2.10 has some * rounding errors, but this is undoubtedly what is meant. */ } exotic_combinator ucombine_GRAIN_EXTRACT(uint8_t bot,uint8_t top) { int temp = (int)bot - (int)top + 128 ; return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; } exotic_combinator ucombine_GRAIN_MERGE(uint8_t bot,uint8_t top) { int temp = (int)bot + (int)top - 128 ; return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; } struct HSV { enum { HUE_RED_GREEN_BLUE,HUE_RED_BLUE_GREEN,HUE_BLUE_RED_GREEN, HUE_BLUE_GREEN_RED,HUE_GREEN_BLUE_RED,HUE_GREEN_RED_BLUE } hue; unsigned ch1, ch2, ch3 ; }; static void RGBtoHSV(rgba rgb,struct HSV *hsv) { unsigned RED = (uint8_t)(rgb >> RED_SHIFT); unsigned GREEN = (uint8_t)(rgb >> GREEN_SHIFT); unsigned BLUE = (uint8_t)(rgb >> BLUE_SHIFT) ; #define HEXTANT(b,m,t) hsv->ch1 = b, hsv->ch2 = m, hsv->ch3 = t, \ hsv->hue = HUE_ ## b ## _ ## m ## _ ## t if( GREEN <= RED ) if( BLUE <= RED ) if( GREEN <= BLUE ) HEXTANT(GREEN,BLUE,RED); else HEXTANT(BLUE,GREEN,RED); else HEXTANT(GREEN,RED,BLUE); else if( BLUE <= RED ) HEXTANT(BLUE,RED,GREEN); else if( BLUE <= GREEN ) HEXTANT(RED,BLUE,GREEN); else HEXTANT(RED,GREEN,BLUE); #undef HEXTANT } /* merge_exotic() destructively updates bot. * merge_exotic() reads but does not free top. */ static int __ATTRIBUTE__((noinline)) merge_exotic(struct Tile *bot, const struct Tile *top, GimpLayerModeEffects mode) { unsigned i ; assertTileCompatibility(bot,top); if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK; if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK; assert( bot->refcount == 1 ); /* The transparency status of bot never changes */ INIT_SCALETABLE_IF(1); for( i=0; i < top->count ; i++ ) { uint32_t RED, GREEN, BLUE ; if( NULLALPHA(bot->pixels[i]) || NULLALPHA(top->pixels[i]) ) continue ; #define UNIFORM(mode) case GIMP_ ## mode ## _MODE: \ RED = ucombine_ ## mode (bot->pixels[i]>>RED_SHIFT , \ top->pixels[i]>>RED_SHIFT ); \ GREEN = ucombine_ ## mode (bot->pixels[i]>>GREEN_SHIFT, \ top->pixels[i]>>GREEN_SHIFT); \ BLUE = ucombine_ ## mode (bot->pixels[i]>>BLUE_SHIFT , \ top->pixels[i]>>BLUE_SHIFT ); \ break ; switch( mode ) { case GIMP_NORMAL_MODE: case GIMP_DISSOLVE_MODE: { FatalUnexpected("Normal and Dissolve mode can't happen here!"); return XCF_ERROR; } UNIFORM(ADDITION); UNIFORM(SUBTRACT); UNIFORM(LIGHTEN_ONLY); UNIFORM(DARKEN_ONLY); UNIFORM(DIFFERENCE); UNIFORM(MULTIPLY); UNIFORM(DIVIDE); UNIFORM(SCREEN); case GIMP_SOFTLIGHT_MODE: /* A synonym for "overlay"! */ UNIFORM(OVERLAY); UNIFORM(DODGE); UNIFORM(BURN); UNIFORM(HARDLIGHT); UNIFORM(GRAIN_EXTRACT); UNIFORM(GRAIN_MERGE); case GIMP_HUE_MODE: case GIMP_SATURATION_MODE: case GIMP_VALUE_MODE: case GIMP_COLOR_MODE: { static struct HSV hsvTop, hsvBot ; RGBtoHSV(top->pixels[i],&hsvTop); if( mode == GIMP_HUE_MODE && hsvTop.ch1 == hsvTop.ch3 ) continue ; RGBtoHSV(bot->pixels[i],&hsvBot); if( mode == GIMP_VALUE_MODE ) { if( hsvBot.ch3 ) { hsvBot.ch1 = (hsvBot.ch1*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3; hsvBot.ch2 = (hsvBot.ch2*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3; hsvBot.ch3 = hsvTop.ch3 ; } else { hsvBot.ch1 = hsvBot.ch2 = hsvBot.ch3 = hsvTop.ch3 ; } } else { unsigned mfNum, mfDenom ; if( mode == GIMP_HUE_MODE || mode == GIMP_COLOR_MODE ) { mfNum = hsvTop.ch2-hsvTop.ch1 ; mfDenom = hsvTop.ch3-hsvTop.ch1 ; hsvBot.hue = hsvTop.hue ; } else { mfNum = hsvBot.ch2-hsvBot.ch1 ; mfDenom = hsvBot.ch3-hsvBot.ch1 ; } if( mode == GIMP_SATURATION_MODE ) { if( hsvTop.ch3 == 0 ) hsvBot.ch1 = hsvBot.ch3 ; /* Black has no saturation */ else hsvBot.ch1 = (hsvTop.ch1*hsvBot.ch3 + hsvTop.ch3/2) / hsvTop.ch3; } else if( mode == GIMP_COLOR_MODE ) { /* GIMP_COLOR_MODE works in HSL space instead of HSV. We must * transfer H and S, keeping the L = ch1+ch3 of the bottom pixel, * but the S we transfer works differently from the S in HSV. */ unsigned L = hsvTop.ch1 + hsvTop.ch3 ; unsigned sNum = hsvTop.ch3 - hsvTop.ch1 ; unsigned sDenom = L < 256 ? L : 510-L ; if( sDenom == 0 ) sDenom = 1 ; /* sNum will be 0 */ L = hsvBot.ch1 + hsvBot.ch3 ; if( L < 256 ) { /* Ideally we want to compute L/2 * (1-sNum/sDenom) * But shuffle this a bit so we can use integer arithmetic. * The "-1" in the rounding prevents us from ending up with * ch1 > ch3. */ hsvBot.ch1 = (L*(sDenom-sNum)+sDenom-1)/(2*sDenom); hsvBot.ch3 = L - hsvBot.ch1 ; } else { /* Here our goal is 255 - (510-L)/2 * (1-sNum/sDenom) */ hsvBot.ch3 = 255 - ((510-L)*(sDenom-sNum)+sDenom-1)/(2*sDenom); hsvBot.ch1 = L - hsvBot.ch3 ; } assert(hsvBot.ch3 <= 255); assert(hsvBot.ch3 >= hsvBot.ch1); } if( mfDenom == 0 ) hsvBot.ch2 = hsvBot.ch1 ; else hsvBot.ch2 = hsvBot.ch1 + (mfNum*(hsvBot.ch3-hsvBot.ch1) + mfDenom/2) / mfDenom ; } switch( hsvBot.hue ) { #define HEXTANT(b,m,t) case HUE_ ## b ## _ ## m ## _ ## t : \ b = hsvBot.ch1; m = hsvBot.ch2; t = hsvBot.ch3; break; HEXTANT(RED,GREEN,BLUE); HEXTANT(RED,BLUE,GREEN); HEXTANT(BLUE,RED,GREEN); HEXTANT(BLUE,GREEN,RED); HEXTANT(GREEN,BLUE,RED); HEXTANT(GREEN,RED,BLUE); #undef HEXTANT default: { FatalUnexpected("Hue hextant is %d", hsvBot.hue); return XCF_ERROR; } } break ; } default: { FatalUnsupportedXCF(_("'%s' layer mode"), _(showGimpLayerModeEffects(mode))); return XCF_ERROR; } } if( FULLALPHA(bot->pixels[i] & top->pixels[i]) ) bot->pixels[i] = (bot->pixels[i] & (255 << ALPHA_SHIFT)) + (RED << RED_SHIFT) + (GREEN << GREEN_SHIFT) + (BLUE << BLUE_SHIFT) ; else { rgba bp = bot->pixels[i] ; /* In a sane world, the alpha of the top pixel would simply be * used to interpolate linearly between the bottom pixel's base * color and the effect-computed color. * But no! What the Gimp actually does is empirically * described by the following (which borrows code from * composite_one() that makes no theoretical sense here): */ unsigned tfrac = ALPHA(top->pixels[i]) ; if( !FULLALPHA(bp) ) { unsigned pseudotop = (tfrac < ALPHA(bp) ? tfrac : ALPHA(bp)); unsigned alpha = 255 ^ scaletable[255-ALPHA(bp)][255-pseudotop] ; tfrac = (256*pseudotop - 1) / alpha ; } bot->pixels[i] = (bp & (255 << ALPHA_SHIFT)) + ((rgba)scaletable[ tfrac ][ RED ] << RED_SHIFT ) + ((rgba)scaletable[ tfrac ][ GREEN ] << GREEN_SHIFT) + ((rgba)scaletable[ tfrac ][ BLUE ] << BLUE_SHIFT ) + ((rgba)scaletable[255^tfrac][255&(bp>>RED_SHIFT )] << RED_SHIFT ) + ((rgba)scaletable[255^tfrac][255&(bp>>GREEN_SHIFT)] << GREEN_SHIFT) + ((rgba)scaletable[255^tfrac][255&(bp>>BLUE_SHIFT )] << BLUE_SHIFT ) ; } } return XCF_OK; } static void dissolveTile(struct Tile *tile) { unsigned i ; summary_t summary ; assert( tile->refcount == 1 ); if( (tile->summary & TILESUMMARY_CRISP) ) return ; summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i = 0 ; i < tile->count ; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if ( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else if( ALPHA(tile->pixels[i]) > rand() % 0xFF ) { tile->pixels[i] |= 255 << ALPHA_SHIFT ; summary &= ~TILESUMMARY_ALLNULL ; } else { tile->pixels[i] = 0 ; summary &= ~TILESUMMARY_ALLFULL ; } } tile->summary = summary ; } static void roundAlpha(struct Tile *tile) { unsigned i ; summary_t summary ; assert( tile->refcount == 1 ); if( (tile->summary & TILESUMMARY_CRISP) ) return ; summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i = 0 ; i < tile->count ; i++ ) { if( ALPHA(tile->pixels[i]) >= 128 ) { tile->pixels[i] |= 255 << ALPHA_SHIFT ; summary &= ~TILESUMMARY_ALLNULL ; } else { tile->pixels[i] = 0 ; summary &= ~TILESUMMARY_ALLFULL ; } } tile->summary = summary ; } /* flattenTopdown() shares ownership of top. * The return value may be a shared tile. */ static struct Tile * flattenTopdown(struct FlattenSpec *spec, struct Tile *top, unsigned nlayers, const struct rect *where) { - struct Tile *tile; + struct Tile *tile = 0; while( nlayers-- ) { - if( tileSummary(top) & TILESUMMARY_ALLFULL ) + if( tileSummary(top) & TILESUMMARY_ALLFULL ) { + freeTile(tile); return top ; + } if( !spec->layers[nlayers].isVisible ) continue ; tile = getLayerTile(&spec->layers[nlayers],where); if (tile == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } if( tile->summary & TILESUMMARY_ALLNULL ) continue ; /* Simulate a tail call */ switch( spec->layers[nlayers].mode ) { case GIMP_NORMAL_NOPARTIAL_MODE: roundAlpha(tile) ; /* Falls through */ case GIMP_DISSOLVE_MODE: dissolveTile(tile); /* Falls through */ case GIMP_NORMAL_MODE: top = merge_normal(tile,top); break ; default: { struct Tile *below, *above ; unsigned i ; if( !(top->summary & TILESUMMARY_ALLNULL) ) { rgba tile_or = 0 ; invalidateSummary(tile,0); for( i=0; icount; i++ ) if( FULLALPHA(top->pixels[i]) ) tile->pixels[i] = 0 ; else tile_or |= tile->pixels[i] ; /* If the tile only has pixels that will be covered by 'top' anyway, * forget it anyway. */ if( ALPHA(tile_or) == 0 ) { freeTile(tile); break ; /* from the switch, which will continue the while */ } } /* Create a dummy top for the layers below this */ if( top->summary & TILESUMMARY_CRISP ) { above = forkTile(top); if(above == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } } else { summary_t summary = TILESUMMARY_ALLNULL ; above = newTile(*where); for( i=0; icount; i++ ) if( FULLALPHA(top->pixels[i]) ) { above->pixels[i] = -1 ; summary = 0 ; } else above->pixels[i] = 0 ; above->summary = TILESUMMARY_UPTODATE + TILESUMMARY_CRISP + summary; } below = flattenTopdown(spec, above, nlayers, where); if (below == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } if( below->refcount > 1 ) { if (below != top) { return XCF_PTR_EMPTY; } /* This can only happen if 'below' is a copy of 'top' * THROUGH 'above', which in turn means that none of all * this is visible after all. So just free it and return 'top'. */ freeTile(below); return top ; } if (merge_exotic(below,tile,spec->layers[nlayers].mode) != XCF_OK) { return XCF_PTR_EMPTY; } freeTile(tile); top = merge_normal(below,top); return top ; } } } return top ; } static int addBackground(struct FlattenSpec *spec, struct Tile *tile, unsigned ncols) { unsigned i ; if( tileSummary(tile) & TILESUMMARY_ALLFULL ) return XCF_OK; switch( spec->partial_transparency_mode ) { case FORBID_PARTIAL_TRANSPARENCY: if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) { FatalGeneric(102,_("Flattened image has partially transparent pixels")); return XCF_ERROR; } break ; case DISSOLVE_PARTIAL_TRANSPARENCY: dissolveTile(tile); break ; case ALLOW_PARTIAL_TRANSPARENCY: case PARTIAL_TRANSPARENCY_IMPOSSIBLE: break ; } if( spec->default_pixel == CHECKERED_BACKGROUND ) { INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP ) ); for( i=0; icount; i++ ) if( !FULLALPHA(tile->pixels[i]) ) { rgba fillwith = ((i/ncols)^(i%ncols))&8 ? 0x66 : 0x99 ; fillwith = graytable[fillwith] + (255 << ALPHA_SHIFT) ; if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = fillwith ; else tile->pixels[i] = composite_one(fillwith,tile->pixels[i]); } tile->summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; return XCF_OK; } if( !FULLALPHA(spec->default_pixel) ) return XCF_OK; if( tileSummary(tile) & TILESUMMARY_ALLNULL ) { fillTile(tile,spec->default_pixel); } else { INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP) ); for( i=0; icount; i++ ) if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = spec->default_pixel ; else if( FULLALPHA(tile->pixels[i]) ) ; else tile->pixels[i] = composite_one(spec->default_pixel,tile->pixels[i]); tile->summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; } return XCF_OK; } int flattenIncrementally(struct FlattenSpec *spec,lineCallback callback) { rgba *rows[TILE_HEIGHT] ; unsigned i, y, nrows, ncols ; struct rect where ; struct Tile *tile ; static struct Tile toptile ; toptile.count = TILE_HEIGHT * TILE_WIDTH ; fillTile(&toptile,0); for( where.t = spec->dim.c.t; where.t < spec->dim.c.b; where.t=where.b ) { where.b = TILE_TOP(where.t)+TILE_HEIGHT ; if( where.b > spec->dim.c.b ) where.b = spec->dim.c.b ; nrows = where.b - where.t ; for( y = 0; y < nrows ; y++ ) rows[y] = xcfmalloc(4*(spec->dim.c.r-spec->dim.c.l)); for( where.l = spec->dim.c.l; where.l < spec->dim.c.r; where.l=where.r ) { where.r = TILE_LEFT(where.l)+TILE_WIDTH ; if( where.r > spec->dim.c.r ) where.r = spec->dim.c.r ; ncols = where.r - where.l ; toptile.count = ncols * nrows ; toptile.refcount = 2 ; /* For bug checking */ assert( toptile.summary == TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_CRISP ); tile = flattenTopdown(spec,&toptile,spec->numLayers,&where) ; if (tile == XCF_PTR_EMPTY) { return XCF_ERROR; } toptile.refcount-- ; /* addBackground may change destructively */ if (addBackground(spec,tile,ncols) != XCF_OK) { return XCF_ERROR; } for( i = 0 ; i < tile->count ; i++ ) if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = 0 ; for( y = 0 ; y < nrows ; y++ ) memcpy(rows[y] + (where.l - spec->dim.c.l), tile->pixels + y * ncols, ncols*4); if( tile == &toptile ) { fillTile(&toptile,0); } else { freeTile(tile); } } for( y = 0 ; y < nrows ; y++ ) callback(spec->dim.width,rows[y]); } return XCF_OK; } static rgba **collectPointer ; static void collector(unsigned num,rgba *row) { num += 0; *collectPointer++ = row ; } rgba ** flattenAll(struct FlattenSpec *spec) { rgba **rows = xcfmalloc(spec->dim.height * sizeof(rgba*)); if( verboseFlag ) fprintf(stderr,_("Flattening image ...")); collectPointer = rows ; if (flattenIncrementally(spec,collector) != XCF_OK) { xcffree(rows); collectPointer = XCF_PTR_EMPTY; return XCF_PTR_EMPTY; } if( verboseFlag ) fprintf(stderr,"\n"); return rows ; } void shipoutWithCallback(struct FlattenSpec *spec, rgba **pixels, lineCallback callback) { unsigned i ; for( i = 0; i < spec->dim.height; i++ ) { callback(spec->dim.width,pixels[i]); } xcffree(pixels); } diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.c b/plugins/impex/xcf/3rdparty/xcftools/pixels.c index 51999520f2..f671423561 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/pixels.c +++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.c @@ -1,583 +1,584 @@ /* Pixel and tile functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #define DEBUG #include "xcftools.h" #include "pixels.h" #include #include rgba colormap[256] ; unsigned colormapLength=0 ; int degrayPixel(rgba pixel) { if( ((pixel >> RED_SHIFT) & 255) == ((pixel >> GREEN_SHIFT) & 255) && ((pixel >> RED_SHIFT) & 255) == ((pixel >> BLUE_SHIFT) & 255) ) return (pixel >> RED_SHIFT) & 255 ; return -1 ; } /* ****************************************************************** */ typedef const struct _convertParams { int bpp ; int shift[4] ; uint32_t base_pixel ; const rgba *lookup ; } convertParams ; #define RGB_SHIFT RED_SHIFT, GREEN_SHIFT, BLUE_SHIFT #define OPAQUE (255 << ALPHA_SHIFT) static convertParams convertRGB = { 3, {RGB_SHIFT}, OPAQUE, 0 }; static convertParams convertRGBA = { 4, {RGB_SHIFT, ALPHA_SHIFT}, 0,0 }; static convertParams convertGRAY = { 1, {-1}, OPAQUE, graytable }; static convertParams convertGRAYA = { 2, {-1,ALPHA_SHIFT}, 0, graytable }; static convertParams convertINDEXED = { 1, {-1}, OPAQUE, colormap }; static convertParams convertINDEXEDA = { 2, {-1,ALPHA_SHIFT}, 0, colormap }; static convertParams convertColormap = { 3, {RGB_SHIFT}, 0, 0 }; static convertParams convertChannel = { 1, {ALPHA_SHIFT}, 0, 0 }; /* ****************************************************************** */ static int tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr, int* ptrOut) { if( ptr == 0 ) { *ptrOut = 0; return XCF_OK; /* allowed by xcf, apparently */ } if( xcfL(ptr ) != dim->c.r - dim->c.l || xcfL(ptr+4) != dim->c.b - dim->c.t ) { FatalBadXCF("Drawable size mismatch at %" PRIX32, ptr); *ptrOut = XCF_PTR_EMPTY; return XCF_ERROR; } *ptrOut = (ptr += 8) ; return XCF_OK; } static int initTileDirectory(struct tileDimensions *dim,struct xcfTiles *tiles, const char *type) { uint32_t ptr ; uint32_t data ; ptr = tiles->hierarchy ; tiles->hierarchy = 0 ; int ptrOut; if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { return XCF_ERROR; } if (ptrOut == XCF_PTR_EMPTY) { return XCF_OK; } ptr = ptrOut; if( tiles->params == &convertChannel ) { /* A layer mask is a channel. * Skip a name and a property list. */ xcfString(ptr,&ptr); PropType type; int response; while( (response = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { } if (response != XCF_OK) { return XCF_ERROR; } uint32_t ptrout; if(xcfOffset(ptr,4*4, &ptrout) != XCF_OK) return XCF_ERROR; ptr = ptrout; if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { return XCF_ERROR; } if (ptrOut == XCF_PTR_EMPTY) { return XCF_OK; } ptr = ptrOut; } /* The XCF format has a dummy "hierarchy" level which was * once meant to mean something, but never happened. It contains * the bpp value and a list of "level" pointers; but only the * first level actually contains data. */ data = xcfL(ptr) ; if( xcfL(ptr) != tiles->params->bpp ) { FatalBadXCF("%"PRIu32" bytes per pixel for %s drawable",xcfL(ptr),type); return XCF_ERROR; } uint32_t ptrout; if(xcfOffset(ptr+4,3*4, &ptrout) != XCF_OK) return XCF_ERROR; ptr = ptrout; if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { return XCF_ERROR; } if (ptrOut == XCF_PTR_EMPTY) { return XCF_OK; } ptr = ptrOut; if (xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr) != XCF_OK) { return XCF_ERROR; } /* if( xcfL(ptr + dim->ntiles*4) != 0 ) FatalBadXCF("Wrong sized tile directory at %" PRIX32,ptr);*/ #define REUSE_RAW_DATA tiles->tileptrs = (uint32_t*)(xcf_file + ptr) #if defined(WORDS_BIGENDIAN) && defined(CAN_DO_UNALIGNED_WORDS) REUSE_RAW_DATA; #else # if defined(WORDS_BIGENDIAN) if( (ptr&3) == 0 ) REUSE_RAW_DATA; else # endif { unsigned i ; tiles->tileptrs = xcfmalloc(dim->ntiles * sizeof(uint32_t)) ; for( i = 0 ; i < dim->ntiles ; i++ ) tiles->tileptrs[i] = xcfL(ptr+i*4); } #endif return XCF_OK; } int initLayer(struct xcfLayer *layer) { if( layer->dim.ntiles == 0 || (layer->pixels.hierarchy == 0 && layer->mask.hierarchy == 0) ) return XCF_OK; switch(layer->type) { #define DEF(X) case GIMP_##X##_IMAGE: layer->pixels.params = &convert##X; break DEF(RGB); DEF(RGBA); DEF(GRAY); DEF(GRAYA); DEF(INDEXED); DEF(INDEXEDA); default: { FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type))); return XCF_ERROR; } } if (initTileDirectory(&layer->dim,&layer->pixels, _(showGimpImageType(layer->type))) != XCF_OK) { return XCF_ERROR; } layer->mask.params = &convertChannel ; if (initTileDirectory(&layer->dim,&layer->mask,"layer mask") != XCF_OK) { return XCF_ERROR; } return XCF_OK; } static int copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params); int initColormap(void) { uint32_t ncolors ; if( XCF.colormapptr == 0 ) { colormapLength = 0 ; return XCF_OK; } ncolors = xcfL(XCF.colormapptr) ; if( ncolors > 256 ) { FatalUnsupportedXCF(_("Color map has more than 256 entries")); return XCF_ERROR; } if(copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap) != XCF_OK) { return XCF_ERROR; } colormapLength = ncolors ; #ifdef xDEBUG { unsigned j ; fprintf(stderr,"Colormap decoding OK\n"); for( j = 0 ; j < ncolors ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %08x",colormap[j]); } fprintf(stderr,"\n"); } #endif return XCF_OK; } /* ****************************************************************** */ struct Tile * newTile(struct rect r) { unsigned npixels = (unsigned)(r.b-r.t) * (unsigned)(r.r-r.l) ; struct Tile *data = xcfmalloc(sizeof(struct Tile) - sizeof(rgba)*(TILE_HEIGHT*TILE_WIDTH - npixels)) ; data->count = npixels ; data->refcount = 1 ; data->summary = 0 ; return data ; } struct Tile * forkTile(struct Tile* tile) { if( ++tile->refcount <= 0 ) { FatalUnsupportedXCF(_("Unbelievably many layers?\n" "More likely to be a bug in %s"),progname); return XCF_PTR_EMPTY; } return tile ; } void freeTile(struct Tile* tile) { if( --tile->refcount == 0 ) xcffree(tile) ; } summary_t tileSummary(struct Tile *tile) { unsigned i ; summary_t summary ; if( (tile->summary & TILESUMMARY_UPTODATE) != 0 ) return tile->summary ; summary = TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i=0; summary && icount; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else summary = 0 ; } summary += TILESUMMARY_UPTODATE ; tile->summary = summary ; return summary ; } void fillTile(struct Tile *tile,rgba data) { unsigned i ; for( i = 0 ; i < tile->count ; i++ ) tile->pixels[i] = data ; if( FULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else if (NULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLNULL+TILESUMMARY_CRISP; else tile->summary = TILESUMMARY_UPTODATE ; } /* ****************************************************************** */ static int copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params) { unsigned bpp = params->bpp; const rgba *lookup = params->lookup; rgba base_pixel = params->base_pixel ; uint8_t *bp = xcf_file + ptr ; int response; if ((response = xcfCheckspace(ptr,bpp*npixels, "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr)) != XCF_OK) { return XCF_ERROR; } while( npixels-- ) { rgba pixel = base_pixel ; unsigned i ; for( i = 0 ; i < bpp ; i++ ) { if( params->shift[i] < 0 ) { pixel += lookup[*bp++] ; } else { pixel += *bp++ << params->shift[i] ; } } *dest++ = pixel ; } return XCF_OK; } static int copyRLEpixels(rgba *dest,unsigned npixels,uint32_t ptr,convertParams *params) { unsigned i,j ; rgba base_pixel = params->base_pixel ; #ifdef xDEBUG fprintf(stderr,"RLE stream at %x, want %u x %u pixels, base %x\n", ptr,params->bpp,npixels,base_pixel); #endif /* This algorithm depends on the indexed byte always being the first one */ if( params->shift[0] < -1 ) base_pixel = 0 ; for( j = npixels ; j-- ; ) dest[j] = base_pixel ; for( i = 0 ; i < params->bpp ; i++ ) { int shift = params->shift[i] ; if( shift < 0 ) shift = 0 ; for( j = 0 ; j < npixels ; ) { int countspec ; unsigned count ; if (xcfCheckspace(ptr,2,"RLE data stream") != XCF_OK) { return XCF_ERROR; } countspec = (int8_t) xcf_file[ptr++] ; count = countspec >= 0 ? countspec+1 : -countspec ; if( count == 128 ) { if (xcfCheckspace(ptr,3,"RLE long count") != XCF_OK) { return XCF_ERROR; } count = xcf_file[ptr++] << 8 ; count += xcf_file[ptr++] ; } if( j + count > npixels ) { FatalBadXCF("Overlong RLE run at %"PRIX32" (plane %u, %u left)", ptr,i,npixels-j); return XCF_ERROR; } if( countspec >= 0 ) { rgba data = (uint32_t) xcf_file[ptr++] << shift ; while( count-- ) dest[j++] += data ; } else { while( count-- ) dest[j++] += (uint32_t) xcf_file[ptr++] << shift ; } } if( i == 0 && params->shift[0] < 0 ) { const rgba *lookup = params->lookup ; base_pixel = params->base_pixel ; for( j = npixels ; j-- ; ) { dest[j] = lookup[dest[j]-base_pixel] + base_pixel ; } } } #ifdef xDEBUG fprintf(stderr,"RLE decoding OK at %"PRIX32"\n",ptr); /* for( j = 0 ; j < npixels ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %8x",dest[j]); } fprintf(stderr,"\n"); */ #endif return XCF_OK; } static int copyTilePixels(struct Tile *dest, uint32_t ptr,convertParams *params) { if( FULLALPHA(params->base_pixel) ) dest->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else dest->summary = 0 ; switch( XCF.compression ) { case COMPRESS_NONE: if (copyStraightPixels(dest->pixels,dest->count,ptr,params) != XCF_OK) { return XCF_ERROR; } break ; case COMPRESS_RLE: if (copyRLEpixels(dest->pixels,dest->count,ptr,params) != XCF_OK) { return XCF_ERROR; } break ; default: { FatalUnsupportedXCF(_("%s compression"), _(showXcfCompressionType(XCF.compression))); return XCF_ERROR; } } return XCF_OK; } struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want) { struct Tile *tile = newTile(want); if (want.l >= want.r || want.t >= want.b ) { freeTile(tile); return XCF_PTR_EMPTY; } if( tiles->tileptrs == 0 ) { fillTile(tile,0); return tile ; } #ifdef xDEBUG fprintf(stderr,"getMaskOrLayer: (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif if( isSubrect(want,dim->c) && (want.l - dim->c.l) % TILE_WIDTH == 0 && (want.t - dim->c.t) % TILE_HEIGHT == 0 ) { int tx = TILE_NUM(want.l - dim->c.l); int ty = TILE_NUM(want.t - dim->c.t); if( want.r == TILEXn(*dim,tx+1) && want.b == TILEYn(*dim,ty+1) ) { /* The common case? An entire single tile from the layer */ if (copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params) != XCF_OK) { freeTile(tile); return XCF_PTR_EMPTY; } return tile ; } } /* OK, we must construct the wanted tile as a jigsaw */ { unsigned width = want.r-want.l ; rgba *pixvert = tile->pixels ; rgba *pixhoriz ; int y, ty, l0, l1 ; int x, tx, c0, c1 ; unsigned lstart, lnum ; unsigned cstart, cnum ; if( !isSubrect(want,dim->c) ) { if( want.l < dim->c.l ) pixvert += (dim->c.l - want.l), want.l = dim->c.l ; if( want.r > dim->c.r ) want.r = dim->c.r ; if( want.t < dim->c.t ) pixvert += (dim->c.t - want.t) * width, want.t = dim->c.t ; if( want.b > dim->c.b ) want.b = dim->c.b ; fillTile(tile,0); } else { tile->summary = -1 ; /* I.e. whatever the jigsaw pieces say */ } #ifdef xDEBUG fprintf(stderr,"jig0 (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif for( y=want.t, ty=TILE_NUM(want.t-dim->c.t), l0=TILEYn(*dim,ty); y want.b ? want.b : l1) - y ; pixhoriz = pixvert ; for( x=want.l, tx=TILE_NUM(want.l-dim->c.l), c0=TILEXn(*dim,tx); x want.r ? want.r : c1) - x ; { static struct Tile tmptile ; unsigned dwidth = c1-c0 ; unsigned i, j ; tmptile.count = (c1-c0)*(l1-l0) ; #ifdef xDEBUG fprintf(stderr,"jig ty=%u(%u-%u-%u)(%u+%u) tx=%u(%u-%u-%u)(%u+%u)\n", ty,l0,y,l1,lstart,lnum, tx,c0,x,c1,cstart,cnum); #endif if (copyTilePixels(&tmptile, tiles->tileptrs[tx+ty*dim->tilesx],tiles->params) != XCF_OK) { freeTile(tile); return XCF_PTR_EMPTY; } for(i=0; isummary &= tmptile.summary ; } } } } return tile ; } void applyMask(struct Tile *tile, struct Tile *mask) { unsigned i ; assertTileCompatibility(tile,mask); assert( tile->count == mask->count ); INIT_SCALETABLE_IF(1); invalidateSummary(tile,0); for( i=0; i < tile->count ;i++ ) tile->pixels[i] = NEWALPHA(tile->pixels[i], scaletable[mask->pixels[i]>>ALPHA_SHIFT] [ALPHA(tile->pixels[i])]); freeTile(mask); } struct Tile * getLayerTile(struct xcfLayer *layer,const struct rect *where) { struct Tile *data ; #ifdef xDEBUG fprintf(stderr,"getLayerTile(%s): (%d-%d),(%d-%d)\n", layer->name,where->l,where->r,where->t,where->b); #endif if( disjointRects(*where,layer->dim.c) || layer->opacity == 0 ) { data = newTile(*where); fillTile(data,0); return data ; } data = getMaskOrLayerTile(&layer->dim,&layer->pixels,*where); if (data == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } if( (data->summary & TILESUMMARY_ALLNULL) != 0 ) return data ; if( layer->hasMask ) { struct Tile *mask = getMaskOrLayerTile(&layer->dim,&layer->mask,*where); if (mask == XCF_PTR_EMPTY) { /* error */ + freeTile(data); return XCF_PTR_EMPTY; } applyMask(data,mask); } if( layer->opacity < 255 ) { const uint8_t *ourtable ; int i ; invalidateSummary(data,~(TILESUMMARY_CRISP | TILESUMMARY_ALLFULL)); INIT_SCALETABLE_IF(1); ourtable = scaletable[layer->opacity] ; for( i=0; i < data->count; i++ ) data->pixels[i] = NEWALPHA(data->pixels[i],ourtable[ALPHA(data->pixels[i])]) ; } return data ; } diff --git a/plugins/impex/xcf/kis_xcf_import.cpp b/plugins/impex/xcf/kis_xcf_import.cpp index 41c14801f8..97f6b483a1 100644 --- a/plugins/impex/xcf/kis_xcf_import.cpp +++ b/plugins/impex/xcf/kis_xcf_import.cpp @@ -1,330 +1,331 @@ /* * Copyright (c) 2009 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; 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_xcf_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "kis_types.h" #include extern "C" { #include "xcftools.h" #include "pixels.h" #define GET_RED(x) (x >> RED_SHIFT) #define GET_GREEN(x) (x >> GREEN_SHIFT) #define GET_BLUE(x) (x >> BLUE_SHIFT) #define GET_ALPHA(x) (x >> ALPHA_SHIFT) } QString layerModeG2K(GimpLayerModeEffects mode) { switch (mode) { case GIMP_NORMAL_MODE: return COMPOSITE_OVER; case GIMP_DISSOLVE_MODE: return COMPOSITE_DISSOLVE; case GIMP_MULTIPLY_MODE: return COMPOSITE_MULT; case GIMP_SCREEN_MODE: return COMPOSITE_SCREEN; case GIMP_OVERLAY_MODE: case GIMP_SOFTLIGHT_MODE: return COMPOSITE_OVERLAY; case GIMP_DIFFERENCE_MODE: return COMPOSITE_DIFF; case GIMP_ADDITION_MODE: return COMPOSITE_ADD; case GIMP_SUBTRACT_MODE: return COMPOSITE_SUBTRACT; case GIMP_DARKEN_ONLY_MODE: return COMPOSITE_DARKEN; case GIMP_LIGHTEN_ONLY_MODE: return COMPOSITE_LIGHTEN; case GIMP_HUE_MODE: return COMPOSITE_HUE_HSL; case GIMP_SATURATION_MODE: return COMPOSITE_SATURATION_HSV; case GIMP_COLOR_MODE: return COMPOSITE_COLOR_HSL; case GIMP_VALUE_MODE: return COMPOSITE_VALUE; case GIMP_DIVIDE_MODE: return COMPOSITE_DIVIDE; case GIMP_DODGE_MODE: return COMPOSITE_DODGE; case GIMP_BURN_MODE: return COMPOSITE_BURN; case GIMP_ERASE_MODE: return COMPOSITE_ERASE; case GIMP_REPLACE_MODE: return COMPOSITE_COPY; case GIMP_HARDLIGHT_MODE: return COMPOSITE_HARD_LIGHT; case GIMP_COLOR_ERASE_MODE: case GIMP_NORMAL_NOPARTIAL_MODE: case GIMP_ANTI_ERASE_MODE: case GIMP_GRAIN_EXTRACT_MODE: return COMPOSITE_GRAIN_EXTRACT; case GIMP_GRAIN_MERGE_MODE: return COMPOSITE_GRAIN_MERGE; case GIMP_BEHIND_MODE: break; } dbgFile << "Unknown mode: " << mode; return COMPOSITE_OVER; } struct Layer { KisLayerSP layer; int depth; KisMaskSP mask; }; KisGroupLayerSP findGroup(const QVector &layers, const Layer& layer, int i) { for (; i < layers.size(); ++i) { KisGroupLayerSP group = dynamic_cast(const_cast(layers[i].layer.data())); if (group && (layers[i].depth == layer.depth -1)) { return group; } } return 0; } void addLayers(const QVector &layers, KisImageSP image, int depth) { for(int i = 0; i < layers.size(); i++) { const Layer &layer = layers[i]; if (layer.depth == depth) { KisGroupLayerSP group = (depth == 0 ? image->rootLayer() : findGroup(layers, layer, i)); image->addNode(layer.layer, group); if (layer.mask) { image->addNode(layer.mask, layer.layer); } } } } K_PLUGIN_FACTORY_WITH_JSON(XCFImportFactory, "krita_xcf_import.json", registerPlugin();) KisXCFImport::KisXCFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisXCFImport::~KisXCFImport() { } KisImportExportErrorCode KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { int errorStatus; dbgFile << "Start decoding file"; QByteArray data = io->readAll(); xcf_file = (uint8_t*)data.data(); xcf_length = data.size(); io->close(); // Decode the data if (getBasicXcfInfo() != XCF_OK) { if (XCF.version < 0 || XCF.version > 3) { document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); return ImportExportCodes::FormatFeaturesUnsupported; } return ImportExportCodes::FileFormatIncorrect; } if(initColormap() != XCF_OK) { return ImportExportCodes::FileFormatIncorrect; } dbgFile << XCF.version << "width = " << XCF.width << "height = " << XCF.height << "layers = " << XCF.numLayers; // Create the image KisImageSP image = new KisImage(document->createUndoStore(), XCF.width, XCF.height, KoColorSpaceRegistry::instance()->rgb8(), "built image"); QVector layers; uint maxDepth = 0; // Read layers for (int i = 0; i < XCF.numLayers; ++i) { Layer layer; xcfLayer& xcflayer = XCF.layers[i]; dbgFile << i << " name = " << xcflayer.name << " opacity = " << xcflayer.opacity << "group:" << xcflayer.isGroup << xcflayer.pathLength; dbgFile << ppVar(xcflayer.dim.width) << ppVar(xcflayer.dim.height) << ppVar(xcflayer.dim.tilesx) << ppVar(xcflayer.dim.tilesy) << ppVar(xcflayer.dim.ntiles) << ppVar(xcflayer.dim.c.t) << ppVar(xcflayer.dim.c.l) << ppVar(xcflayer.dim.c.r) << ppVar(xcflayer.dim.c.b); maxDepth = qMax(maxDepth, xcflayer.pathLength); bool isRgbA = false; // Select the color space const KoColorSpace* colorSpace = 0; switch (xcflayer.type) { case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); isRgbA = true; break; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); isRgbA = false; break; } // Create the layer KisLayerSP kisLayer; if (xcflayer.isGroup) { kisLayer = new KisGroupLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity); } else { kisLayer = new KisPaintLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity, colorSpace); } // Set some properties kisLayer->setCompositeOpId(layerModeG2K(xcflayer.mode)); kisLayer->setVisible(xcflayer.isVisible); kisLayer->disableAlphaChannel(xcflayer.mode != GIMP_NORMAL_MODE); layer.layer = kisLayer; layer.depth = xcflayer.pathLength; // Copy the data in the image if ((errorStatus = initLayer(&xcflayer)) != XCF_OK) { return ImportExportCodes::FileFormatIncorrect; } int left = xcflayer.dim.c.l; int top = xcflayer.dim.c.t; if (!xcflayer.isGroup) { // Copy the data; for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.pixels, want); if (tile == XCF_PTR_EMPTY) { return ImportExportCodes::FileFormatIncorrect; } KisHLineIteratorSP it = kisLayer->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { if (isRgbA) { // RGB image do { KoBgrTraits::setRed(it->rawData(), GET_RED(*data)); KoBgrTraits::setGreen(it->rawData(), GET_GREEN(*data)); KoBgrTraits::setBlue(it->rawData(), GET_BLUE(*data)); KoBgrTraits::setOpacity(it->rawData(), quint8(GET_ALPHA(*data)), 1); ++data; } while (it->nextPixel()); } else { // Grayscale image do { it->rawData()[0] = GET_RED(*data); it->rawData()[1] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); } it->nextRow(); } } } // Move the layer to its position kisLayer->paintDevice()->setX(left); kisLayer->paintDevice()->setY(top); } // Create the mask if (xcflayer.hasMask) { KisTransparencyMaskSP mask = new KisTransparencyMask(); layer.mask = mask; mask->initSelection(kisLayer); for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.mask, want); if (tile == XCF_PTR_EMPTY) { + delete tile; return ImportExportCodes::FileFormatIncorrect; } KisHLineIteratorSP it = mask->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { do { it->rawData()[0] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); it->nextRow(); } - + delete tile; } } mask->paintDevice()->setX(left); mask->paintDevice()->setY(top); } dbgFile << xcflayer.pixels.tileptrs; layers.append(layer); } for (uint i = 0; i <= maxDepth; ++i) { addLayers(layers, image, i); } document->setCurrentImage(image); return ImportExportCodes::OK; } #include "kis_xcf_import.moc" diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp index ad559c25a2..d1899edd4b 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp @@ -1,261 +1,261 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 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_duplicateop_settings.h" #include "kis_duplicateop_option.h" #include "kis_duplicateop_settings_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include KisDuplicateOpSettings::KisDuplicateOpSettings() : m_isOffsetNotUptodate(false), m_duringPaintingStroke(false) { } KisDuplicateOpSettings::~KisDuplicateOpSettings() { } bool KisDuplicateOpSettings::paintIncremental() { return false; } QString KisDuplicateOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_COPY; } QPointF KisDuplicateOpSettings::offset() const { return m_offset; } QPointF KisDuplicateOpSettings::position() const { return m_position; } bool KisDuplicateOpSettings::mousePressEvent(const KisPaintInformation &info, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { bool ignoreEvent = true; if (modifiers & Qt::ControlModifier) { if (!m_sourceNode || !(modifiers & Qt::AltModifier)) { m_sourceNode = currentNode; } m_position = info.pos(); m_isOffsetNotUptodate = true; ignoreEvent = false; } else { bool resetOrigin = getBool(DUPLICATE_RESET_SOURCE_POINT); if (m_isOffsetNotUptodate || resetOrigin) { m_offset = info.pos() - m_position; m_isOffsetNotUptodate = false; } m_duringPaintingStroke = true; ignoreEvent = true; } return ignoreEvent; } bool KisDuplicateOpSettings::mouseReleaseEvent() { m_duringPaintingStroke = false; bool ignoreEvent = true; return ignoreEvent; } KisNodeWSP KisDuplicateOpSettings::sourceNode() const { return m_sourceNode; } void KisDuplicateOpSettings::activate() { } void KisDuplicateOpSettings::fromXML(const QDomElement& elt) { // First, call the parent class fromXML to make sure all the // properties are saved to the map KisPaintOpSettings::fromXML(elt); m_offset.setX(KisDomUtils::toDouble(elt.attribute("OffsetX", "0.0"))); m_offset.setY(KisDomUtils::toDouble(elt.attribute("OffsetY", "0.0"))); m_isOffsetNotUptodate = false; } void KisDuplicateOpSettings::toXML(QDomDocument& doc, QDomElement& rootElt) const { // Then call the parent class fromXML KisPropertiesConfiguration::toXML(doc, rootElt); rootElt.setAttribute("OffsetX", QString::number(m_offset.x())); rootElt.setAttribute("OffsetY", QString::number(m_offset.y())); } KisPaintOpSettingsSP KisDuplicateOpSettings::clone() const { KisPaintOpSettingsSP setting = KisBrushBasedPaintOpSettings::clone(); - KisDuplicateOpSettings* s = dynamic_cast(setting.data()); + KisDuplicateOpSettings* s = static_cast(setting.data()); s->m_offset = m_offset; s->m_isOffsetNotUptodate = m_isOffsetNotUptodate; s->m_position = m_position; s->m_sourceNode = m_sourceNode; s->m_duringPaintingStroke = m_duringPaintingStroke; return setting; } QPainterPath KisDuplicateOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; OutlineMode forcedMode = mode; if (!forcedMode.isVisible) { forcedMode.isVisible = true; forcedMode.forceCircle = true; } // clone tool should always show an outline path = KisBrushBasedPaintOpSettings::brushOutlineImpl(info, forcedMode, 1.0); QPainterPath copy(path); QRectF rect2 = copy.boundingRect(); bool shouldStayInOrigin = m_isOffsetNotUptodate // the clone brush right now waits for first stroke with a new origin, so stays at origin point || !getBool(DUPLICATE_MOVE_SOURCE_POINT) // the brush always use the same source point, so stays at origin point || (!m_duringPaintingStroke && getBool(DUPLICATE_RESET_SOURCE_POINT)); // during the stroke, with reset Origin selected, outline should stay at origin point if (shouldStayInOrigin) { copy.translate(m_position - info.pos()); } else { copy.translate(-m_offset); } path.addPath(copy); qreal dx = rect2.width() / 4.0; qreal dy = rect2.height() / 4.0; rect2.adjust(dx, dy, -dx, -dy); path.moveTo(rect2.topLeft()); path.lineTo(rect2.bottomRight()); path.moveTo(rect2.topRight()); path.lineTo(rect2.bottomLeft()); return path; } #include #include "kis_paintop_preset.h" #include "kis_paintop_settings_update_proxy.h" #include "kis_standard_uniform_properties_factory.h" QList KisDuplicateOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(m_uniformProperties); if (props.isEmpty()) { { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "clone_healing", i18n("Healing"), settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); prop->setValue(option.duplicate_healing); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); option.duplicate_healing = prop->value().toBool(); option.writeOptionSetting(prop->settings().data()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "clone_movesource", i18n("Move Source"), settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); prop->setValue(option.duplicate_move_source_point); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); option.duplicate_move_source_point = prop->value().toBool(); option.writeOptionSetting(prop->settings().data()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } } return KisPaintOpSettings::uniformProperties(settings) + props; } diff --git a/plugins/paintops/experiment/kis_experiment_paintop.cpp b/plugins/paintops/experiment/kis_experiment_paintop.cpp index 88c738243f..54764d8233 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.cpp +++ b/plugins/paintops/experiment/kis_experiment_paintop.cpp @@ -1,358 +1,355 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * Copyright (c) 2012 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_experiment_paintop.h" #include "kis_experiment_paintop_settings.h" #include #include #include #include #include #include #include #include KisExperimentPaintOp::KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_firstRun = true; m_experimentOption.readOptionSetting(settings); m_displaceEnabled = m_experimentOption.isDisplacementEnabled; m_displaceCoeff = (m_experimentOption.displacement * 0.01 * 14) + 1; // 1..15 [7 default according alchemy] m_speedEnabled = m_experimentOption.isSpeedEnabled; m_speedMultiplier = (m_experimentOption.speed * 0.01 * 35); // 0..35 [15 default according alchemy] m_smoothingEnabled = m_experimentOption.isSmoothingEnabled; m_smoothingThreshold = m_experimentOption.smoothing; m_useMirroring = painter->hasMirroring(); m_windingFill = m_experimentOption.windingFill; m_hardEdge = m_experimentOption.hardEdge; //Sets the brush to pattern or foregroundColor if (m_experimentOption.fillType == ExperimentFillType::Pattern) { m_fillStyle = KisPainter::FillStylePattern; } else { m_fillStyle = KisPainter::FillStyleForegroundColor; } // Mirror options set with appropriate color, pattern, and fillStyle if (m_useMirroring) { m_originalDevice = source()->createCompositionSourceDevice(); m_originalPainter = new KisPainter(m_originalDevice); m_originalPainter->setCompositeOp(COMPOSITE_COPY); m_originalPainter->setPaintColor(painter->paintColor()); m_originalPainter->setPattern(painter->pattern()); m_originalPainter->setFillStyle(m_fillStyle); - - - } else { m_originalPainter = 0; } } KisExperimentPaintOp::~KisExperimentPaintOp() { delete m_originalPainter; } void KisExperimentPaintOp::paintRegion(const QRegion &changedRegion) { if (m_windingFill) { m_path.setFillRule(Qt::WindingFill); } if (m_useMirroring) { m_originalPainter->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { m_originalPainter->fillPainterPath(m_path, rect); painter()->renderDabWithMirroringNonIncremental(rect, m_originalDevice); } } else { //Sets options when mirror is not selected painter()->setFillStyle(m_fillStyle); painter()->setCompositeOp(COMPOSITE_COPY); painter()->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { painter()->fillPainterPath(m_path, rect); } } } QPointF KisExperimentPaintOp::speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2) { const qreal fadeFactor = 0.6; QPointF diff = pi2.pos() - pi1.pos(); qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); if (realLength < 0.1) return pi2.pos(); qreal coeff = 0.5 * realLength * m_speedMultiplier; m_savedSpeedCoeff = fadeFactor * m_savedSpeedCoeff + (1 - fadeFactor) * coeff; QPointF newPoint = pi1.pos() + diff * m_savedSpeedCoeff / realLength; m_savedSpeedPoint = fadeFactor * m_savedSpeedPoint + (1 - fadeFactor) * newPoint; return m_savedSpeedPoint; } void KisExperimentPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (m_firstRun) { m_firstRun = false; m_path.moveTo(pi1.pos()); m_path.lineTo(pi2.pos()); m_center = pi1.pos(); m_savedUpdateDistance = 0; m_lastPaintTime = 0; m_savedSpeedCoeff = 0; m_savedSpeedPoint = m_center; m_savedSmoothingDistance = 0; m_savedSmoothingPoint = m_center; } else { const QPointF pos1 = pi1.pos(); QPointF pos2 = pi2.pos(); if (m_speedEnabled) { pos2 = speedCorrectedPosition(pi1, pi2); } int length = (pos2 - pos1).manhattanLength(); m_savedUpdateDistance += length; if (m_smoothingEnabled) { m_savedSmoothingDistance += length; if (m_savedSmoothingDistance > m_smoothingThreshold) { QPointF pt = (m_savedSmoothingPoint + pos2) * 0.5; // for updates approximate curve with two lines m_savedPoints << m_path.currentPosition(); m_savedPoints << m_savedSmoothingPoint; m_savedPoints << m_savedSmoothingPoint; m_savedPoints << pt; m_path.quadTo(m_savedSmoothingPoint, pt); m_savedSmoothingPoint = pos2; m_savedSmoothingDistance = 0; } } else { m_path.lineTo(pos2); m_savedPoints << pos1; m_savedPoints << pos2; } if (m_displaceEnabled) { if (m_path.elementCount() % 16 == 0) { QRectF bounds = m_path.boundingRect(); m_path = applyDisplace(m_path, m_displaceCoeff - length); bounds |= m_path.boundingRect(); qreal threshold = simplifyThreshold(bounds); m_path = KritaUtils::trySimplifyPath(m_path, threshold); } else { m_path = applyDisplace(m_path, m_displaceCoeff - length); } } /** * Refresh rate at least 25fps */ const int timeThreshold = 40; const int elapsedTime = pi2.currentTime() - m_lastPaintTime; QRect pathBounds = m_path.boundingRect().toRect(); int distanceMetric = qMax(pathBounds.width(), pathBounds.height()); if (elapsedTime > timeThreshold || (!m_displaceEnabled && m_savedUpdateDistance > distanceMetric / 8)) { if (m_displaceEnabled) { /** * Rendering the path with diff'ed rects is up to two * times more efficient for really huge shapes (tested * on 2000+ px shapes), however for smaller ones doing * paths arithmetics eats too much time. That's why we * choose the method on the base of the size of the * shape. */ const int pathSizeThreshold = 128; QRegion changedRegion; if (distanceMetric < pathSizeThreshold) { QRectF changedRect = m_path.boundingRect().toRect() | m_lastPaintedPath.boundingRect().toRect(); changedRect.adjust(-1, -1, 1, 1); changedRegion = changedRect.toRect(); } else { QPainterPath diff1 = m_path - m_lastPaintedPath; QPainterPath diff2 = m_lastPaintedPath - m_path; changedRegion = KritaUtils::splitPath(diff1 | diff2); } paintRegion(changedRegion); m_lastPaintedPath = m_path; } else if (!m_savedPoints.isEmpty()) { QRegion changedRegion = KritaUtils::splitTriangles(m_center, m_savedPoints); paintRegion(changedRegion); } m_savedPoints.clear(); m_savedUpdateDistance = 0; m_lastPaintTime = pi2.currentTime(); } } } KisSpacingInformation KisExperimentPaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisExperimentPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(1.0); } bool tryMergePoints(QPainterPath &path, const QPointF &startPoint, const QPointF &endPoint, qreal &distance, qreal distanceThreshold, bool lastSegment) { qreal length = (endPoint - startPoint).manhattanLength(); if (lastSegment || length > distanceThreshold) { if (distance != 0) { path.lineTo(startPoint); } distance = 0; return false; } distance += length; if (distance > distanceThreshold) { path.lineTo(endPoint); distance = 0; } return true; } qreal KisExperimentPaintOp::simplifyThreshold(const QRectF &bounds) { qreal maxDimension = qMax(bounds.width(), bounds.height()); return qMax(0.01 * maxDimension, 1.0); } QPointF KisExperimentPaintOp::getAngle(const QPointF& p1, const QPointF& p2, qreal distance) { QPointF diff = p1 - p2; qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); return realLength > 0.5 ? p1 + diff * distance / realLength : p1; } QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int speed) { QPointF lastPoint = path.currentPosition(); QPainterPath newPath; int count = path.elementCount(); int curveElementCounter = 0; QPointF ctrl1; QPointF ctrl2; QPointF endPoint; for (int i = 0; i < count; i++) { QPainterPath::Element e = path.elementAt(i); switch (e.type) { case QPainterPath::MoveToElement: { newPath.moveTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::LineToElement: { newPath.lineTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::CurveToElement: { curveElementCounter = 0; endPoint = getAngle(QPointF(e.x, e.y), lastPoint, speed); break; } case QPainterPath::CurveToDataElement: { curveElementCounter++; if (curveElementCounter == 1) { ctrl1 = getAngle(QPointF(e.x, e.y), lastPoint, speed); } else if (curveElementCounter == 2) { ctrl2 = getAngle(QPointF(e.x, e.y), lastPoint, speed); newPath.cubicTo(ctrl1, ctrl2, endPoint); } break; } } }// for return newPath; } diff --git a/plugins/paintops/experiment/kis_experiment_paintop.h b/plugins/paintops/experiment/kis_experiment_paintop.h index 7f146ffe9b..19dec9b9d8 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.h +++ b/plugins/paintops/experiment/kis_experiment_paintop.h @@ -1,94 +1,94 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * * 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_EXPERIMENT_PAINTOP_H_ #define KIS_EXPERIMENT_PAINTOP_H_ #include #include #include #include "kis_experiment_paintop_settings.h" #include "kis_experimentop_option.h" #include class QPointF; class KisPainter; class KisExperimentPaintOp : public KisPaintOp { public: KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisExperimentPaintOp() override; void paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: void paintRegion(const QRegion &changedRegion); QPointF speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2); static qreal simplifyThreshold(const QRectF &bounds); static QPointF getAngle(const QPointF& p1, const QPointF& p2, qreal distance); static QPainterPath applyDisplace(const QPainterPath& path, int speed); - bool m_displaceEnabled; - int m_displaceCoeff; + bool m_displaceEnabled {false}; + int m_displaceCoeff {0}; QPainterPath m_lastPaintedPath; - bool m_windingFill; - bool m_hardEdge; + bool m_windingFill {false}; + bool m_hardEdge {false}; - bool m_speedEnabled; - int m_speedMultiplier; - qreal m_savedSpeedCoeff; + bool m_speedEnabled {false}; + int m_speedMultiplier {1}; + qreal m_savedSpeedCoeff {1.0}; QPointF m_savedSpeedPoint; - bool m_smoothingEnabled; - int m_smoothingThreshold; + bool m_smoothingEnabled {false}; + int m_smoothingThreshold {1}; QPointF m_savedSmoothingPoint; - int m_savedSmoothingDistance; + int m_savedSmoothingDistance {1}; - int m_savedUpdateDistance; + int m_savedUpdateDistance {1}; QVector m_savedPoints; - int m_lastPaintTime; + int m_lastPaintTime {0}; - bool m_firstRun; + bool m_firstRun {true}; QPointF m_center; QPainterPath m_path; ExperimentOption m_experimentOption; - bool m_useMirroring; - KisPainter *m_originalPainter; + bool m_useMirroring {false}; + KisPainter *m_originalPainter {0}; KisPaintDeviceSP m_originalDevice; - KisPainter::FillStyle m_fillStyle; + KisPainter::FillStyle m_fillStyle {KisPainter::FillStyleNone}; }; #endif // KIS_EXPERIMENT_PAINTOP_H_ diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp index b1c318594d..2655434f52 100644 --- a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp @@ -1,295 +1,299 @@ /* * Copyright (c) 2004,2007,2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * 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 //MSVC requires that Vc come first #include "kis_auto_brush_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_aspect_ratio_locker.h" #define showSlider(input, step) input->setRange(input->minimum(), input->maximum(), step) #include KisAutoBrushWidget::KisAutoBrushWidget(QWidget *parent, const char* name) : KisWdgAutoBrush(parent, name) , m_autoBrush(0) , m_updateCompressor(new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE)) , m_fadeAspectLocker(new KisAspectRatioLocker()) { connect(m_updateCompressor.data(), SIGNAL(timeout()), SLOT(paramChanged())); connect((QObject*)comboBoxShape, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start())); inputRadius->setRange(0.01, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); inputRadius->setExponentRatio(3.0); inputRadius->setSingleStep(1); inputRadius->setValue(5); inputRadius->setSuffix(i18n(" px")); inputRadius->setBlockUpdateSignalOnDrag(true); connect(inputRadius, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputRatio->setRange(0.0, 1.0, 2); inputRatio->setSingleStep(0.1); inputRatio->setValue(1.0); inputRatio->setBlockUpdateSignalOnDrag(true); connect(inputRatio, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputHFade->setRange(0.0, 1.0, 2); inputHFade->setSingleStep(0.1); inputHFade->setValue(0.5); inputVFade->setRange(0.0, 1.0, 2); inputVFade->setSingleStep(0.1); inputVFade->setValue(0.5); aspectButton->setKeepAspectRatio(true); m_fadeAspectLocker->connectSpinBoxes(inputHFade, inputVFade, aspectButton); m_fadeAspectLocker->setBlockUpdateSignalOnDrag(true); connect(m_fadeAspectLocker.data(), SIGNAL(sliderValueChanged()), m_updateCompressor.data(), SLOT(start())); connect(m_fadeAspectLocker.data(), SIGNAL(aspectButtonChanged()), m_updateCompressor.data(), SLOT(start())); inputSpikes->setRange(2, 20); inputSpikes->setValue(2); inputSpikes->setBlockUpdateSignalOnDrag(true); connect(inputSpikes, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start())); inputRandomness->setRange(0, 100); inputRandomness->setValue(0); inputRandomness->setBlockUpdateSignalOnDrag(true); connect(inputRandomness, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputAngle->setRange(0, 360); inputAngle->setSuffix(QChar(Qt::Key_degree)); inputAngle->setValue(0); inputAngle->setBlockUpdateSignalOnDrag(true); connect(inputAngle, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start())); connect(spacingWidget, SIGNAL(sigSpacingChanged()), m_updateCompressor.data(), SLOT(start())); density->setRange(0, 100, 0); density->setSingleStep(1); density->setValue(100); density->setSuffix(i18n("%")); density->setBlockUpdateSignalOnDrag(true); connect(density, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); KisCubicCurve topLeftBottomRightLinearCurve; topLeftBottomRightLinearCurve.setPoint(0, QPointF(0.0, 1.0)); topLeftBottomRightLinearCurve.setPoint(1, QPointF(1.0, 0.0)); + + bool blockedBefore = softnessCurve->blockSignals(true); softnessCurve->setCurve(topLeftBottomRightLinearCurve); + softnessCurve->blockSignals(blockedBefore); + connect(softnessCurve, SIGNAL(modified()), m_updateCompressor.data(), SLOT(start())); m_brush = QImage(1, 1, QImage::Format_RGB32); connect(brushPreview, SIGNAL(clicked()), m_updateCompressor.data(), SLOT(start())); QList ids = KisMaskGenerator::maskGeneratorIds(); for (int i = 0; i < ids.size(); i++) { comboBoxMaskType->insertItem(i, ids[i].name()); } connect(comboBoxMaskType, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start())); connect(comboBoxMaskType, SIGNAL(currentIndexChanged(int)), SLOT(setStackedWidget(int))); setStackedWidget(comboBoxMaskType->currentIndex()); brushPreview->setIconSize(QSize(100, 100)); connect(btnAntialiasing, SIGNAL(toggled(bool)), m_updateCompressor.data(), SLOT(start())); m_updateCompressor->start(); } KisAutoBrushWidget::~KisAutoBrushWidget() { } void KisAutoBrushWidget::resizeEvent(QResizeEvent *) { brushPreview->setMinimumHeight(brushPreview->width()); // dirty hack ! brushPreview->setMaximumHeight(brushPreview->width()); // dirty hack ! } void KisAutoBrushWidget::activate() { m_updateCompressor->start(); } void KisAutoBrushWidget::paramChanged() { KisMaskGenerator* kas; bool antialiasEdges = btnAntialiasing->isChecked(); if (comboBoxMaskType->currentIndex() == 2) { // gaussian brush if (comboBoxShape->currentIndex() == 0) { kas = new KisGaussCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } else { kas = new KisGaussRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } } else if (comboBoxMaskType->currentIndex() == 1) { // soft brush if (comboBoxShape->currentIndex() == 0) { kas = new KisCurveCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges); } else { kas = new KisCurveRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges); } } else {// default == 0 or any other if (comboBoxShape->currentIndex() == 0) { // use index compare instead of comparing a translatable string kas = new KisCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } else { kas = new KisRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } } Q_CHECK_PTR(kas); m_autoBrush = new KisAutoBrush(kas, inputAngle->value() / 180.0 * M_PI, inputRandomness->value() / 100.0, density->value() / 100.0); m_autoBrush->setSpacing(spacingWidget->spacing()); m_autoBrush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush = m_autoBrush->image(); drawBrushPreviewArea(); emit sigBrushChanged(); } void KisAutoBrushWidget::drawBrushPreviewArea() { QImage pi(m_brush); double coeff = 1.0; int bPw = brushPreview->width() - 3; if (pi.width() > bPw) { coeff = bPw / (double)pi.width(); } int bPh = brushPreview->height() - 3; if (pi.height() > coeff * bPh) { coeff = bPh / (double)pi.height(); } if (coeff < 1.0) { pi = pi.scaled((int)(coeff * pi.width()) , (int)(coeff * pi.height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QPixmap p = QPixmap::fromImage(pi); brushPreview->setIcon(QIcon(p)); } void KisAutoBrushWidget::setStackedWidget(int index) { if (index == 1) { stackedWidget->setCurrentIndex(1); } else { stackedWidget->setCurrentIndex(0); } } KisBrushSP KisAutoBrushWidget::brush() { return m_autoBrush; } void KisAutoBrushWidget::setBrush(KisBrushSP brush) { m_autoBrush = brush; m_brush = brush->image(); // XXX: lock, set and unlock the widgets. KisAutoBrush* aBrush = dynamic_cast(brush.data()); KisSignalsBlocker b1(comboBoxShape, comboBoxMaskType); KisSignalsBlocker b2(inputRadius, inputRatio, inputHFade, inputVFade, inputAngle, inputSpikes); KisSignalsBlocker b3(spacingWidget, inputRandomness, density, softnessCurve, btnAntialiasing); if (aBrush->maskGenerator()->type() == KisMaskGenerator::CIRCLE) { comboBoxShape->setCurrentIndex(0); } else if (aBrush->maskGenerator()->type() == KisMaskGenerator::RECTANGLE) { comboBoxShape->setCurrentIndex(1); } else { comboBoxShape->setCurrentIndex(2); } const int mastTypeIndex = comboBoxMaskType->findText(aBrush->maskGenerator()->name()); comboBoxMaskType->setCurrentIndex(mastTypeIndex); setStackedWidget(mastTypeIndex); // adjusting manually because the signals are blocked inputRadius->setValue(aBrush->maskGenerator()->diameter()); inputRatio->setValue(aBrush->maskGenerator()->ratio()); inputHFade->setValue(aBrush->maskGenerator()->horizontalFade()); inputVFade->setValue(aBrush->maskGenerator()->verticalFade()); inputAngle->setValue(aBrush->angle() * 180 / M_PI); inputSpikes->setValue(aBrush->maskGenerator()->spikes()); spacingWidget->setSpacing(aBrush->autoSpacingActive(), aBrush->autoSpacingActive() ? aBrush->autoSpacingCoeff() : aBrush->spacing()); inputRandomness->setValue(aBrush->randomness() * 100); density->setValue(aBrush->density() * 100); if (!aBrush->maskGenerator()->curveString().isEmpty()) { KisCubicCurve curve; curve.fromString(aBrush->maskGenerator()->curveString()); softnessCurve->setCurve(curve); } btnAntialiasing->setChecked(aBrush->maskGenerator()->antialiasEdges()); drawBrushPreviewArea(); // sync up what the brush preview area looks like } void KisAutoBrushWidget::setBrushSize(qreal dxPixels, qreal dyPixels) { Q_UNUSED(dyPixels); qreal newWidth = inputRadius->value() + dxPixels; newWidth = qMax(newWidth, qreal(0.1)); inputRadius->setValue(newWidth); } QSizeF KisAutoBrushWidget::brushSize() const { return QSizeF(inputRadius->value(), inputRadius->value() * inputRatio->value()); } #include "moc_kis_auto_brush_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp index 49a8ddfb3a..b7dc342b13 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp @@ -1,334 +1,336 @@ /* * Copyright (c) 2010 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_brush_based_paintop_settings.h" #include #include #include "kis_brush_based_paintop_options_widget.h" #include #include "kis_brush_server.h" #include #include "kis_signals_blocker.h" #include "kis_brush_option.h" #include struct BrushReader { BrushReader(const KisBrushBasedPaintOpSettings *parent) : m_parent(parent) { m_option.readOptionSetting(m_parent); } KisBrushSP brush() { return m_option.brush(); } const KisBrushBasedPaintOpSettings *m_parent; KisBrushOptionProperties m_option; }; struct BrushWriter { BrushWriter(KisBrushBasedPaintOpSettings *parent) : m_parent(parent) { m_option.readOptionSetting(m_parent); } ~BrushWriter() { m_option.writeOptionSetting(m_parent); } KisBrushSP brush() { return m_option.brush(); } KisBrushBasedPaintOpSettings *m_parent; KisBrushOptionProperties m_option; }; KisBrushBasedPaintOpSettings::KisBrushBasedPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::SIZE_OPTION | KisCurrentOutlineFetcher::ROTATION_OPTION | KisCurrentOutlineFetcher::MIRROR_OPTION) { } bool KisBrushBasedPaintOpSettings::paintIncremental() { if (hasProperty("PaintOpAction")) { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } return true; } KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const { KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy::clone(); KisBrushBasedPaintOpSettingsSP settings = dynamic_cast(_settings.data()); settings->m_savedBrush = 0; return settings; } KisBrushSP KisBrushBasedPaintOpSettings::brush() const { KisBrushSP brush = m_savedBrush; if (!brush) { BrushReader w(this); brush = w.brush(); m_savedBrush = brush; } return brush; } QPainterPath KisBrushBasedPaintOpSettings::brushOutlineImpl(const KisPaintInformation &info, const OutlineMode &mode, qreal additionalScale) { QPainterPath path; if (mode.isVisible) { KisBrushSP brush = this->brush(); if (!brush) return path; qreal finalScale = brush->scale() * additionalScale; QPainterPath realOutline = brush->outline(); if (mode.forceCircle) { QPainterPath ellipse; ellipse.addEllipse(realOutline.boundingRect()); realOutline = ellipse; } path = outlineFetcher()->fetchOutline(info, this, realOutline, mode, finalScale, brush->angle()); if (mode.showTiltDecoration) { const QPainterPath tiltLine = makeTiltIndicator(info, realOutline.boundingRect().center(), realOutline.boundingRect().width() * 0.5, 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, mode, finalScale, 0.0, true, realOutline.boundingRect().center().x(), realOutline.boundingRect().center().y())); } } return path; } QPainterPath KisBrushBasedPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { return brushOutlineImpl(info, mode, 1.0); } bool KisBrushBasedPaintOpSettings::isValid() const { QStringList files = getStringList(KisPaintOpUtils::RequiredBrushFilesListTag); files << getString(KisPaintOpUtils::RequiredBrushFileTag); Q_FOREACH (const QString &file, files) { if (!file.isEmpty()) { KisBrushSP brush = KisBrushServer::instance()->brushServer()->resourceByFilename(file); if (!brush) { return false; } } } return true; } bool KisBrushBasedPaintOpSettings::isLoadable() { return (KisBrushServer::instance()->brushServer()->resources().count() > 0); } void KisBrushBasedPaintOpSettings::setAngle(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setAngle(value); } qreal KisBrushBasedPaintOpSettings::angle() { return this->brush()->angle(); } void KisBrushBasedPaintOpSettings::setSpacing(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setSpacing(value); } qreal KisBrushBasedPaintOpSettings::spacing() { return this->brush()->spacing(); } void KisBrushBasedPaintOpSettings::setAutoSpacing(bool active, qreal coeff) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setAutoSpacing(active, coeff); } bool KisBrushBasedPaintOpSettings::autoSpacingActive() { return this->brush()->autoSpacingActive(); } qreal KisBrushBasedPaintOpSettings::autoSpacingCoeff() { return this->brush()->autoSpacingCoeff(); } void KisBrushBasedPaintOpSettings::setPaintOpSize(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setUserEffectiveSize(value); } qreal KisBrushBasedPaintOpSettings::paintOpSize() const { return this->brush()->userEffectiveSize(); } #include #include "kis_paintop_preset.h" #include "kis_paintop_settings_update_proxy.h" QList KisBrushBasedPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(m_uniformProperties); if (props.isEmpty()) { { KisIntSliderBasedPaintOpPropertyCallback *prop = new KisIntSliderBasedPaintOpPropertyCallback( KisIntSliderBasedPaintOpPropertyCallback::Int, "angle", "Angle", settings, 0); prop->setRange(0, 360); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); const qreal angleResult = kisRadiansToDegrees(s->angle()); prop->setValue(angleResult); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); s->setAngle(kisDegreesToRadians(prop->value().toReal())); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "auto_spacing", "Auto Spacing", settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); prop->setValue(s->autoSpacingActive()); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); s->setAutoSpacing(prop->value().toBool(), s->autoSpacingCoeff()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisDoubleSliderBasedPaintOpPropertyCallback *prop = new KisDoubleSliderBasedPaintOpPropertyCallback( KisDoubleSliderBasedPaintOpPropertyCallback::Double, "spacing", "Spacing", settings, 0); prop->setRange(0.01, 10); prop->setSingleStep(0.01); prop->setExponentRatio(3.0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); - - const qreal value = s->autoSpacingActive() ? - s->autoSpacingCoeff() : s->spacing(); - prop->setValue(value); + if (s) { + const qreal value = s->autoSpacingActive() ? + s->autoSpacingCoeff() : s->spacing(); + prop->setValue(value); + } }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); - - if (s->autoSpacingActive()) { - s->setAutoSpacing(true, prop->value().toReal()); - } else { - s->setSpacing(prop->value().toReal()); + if (s) { + if (s->autoSpacingActive()) { + s->setAutoSpacing(true, prop->value().toReal()); + } else { + s->setSpacing(prop->value().toReal()); + } } }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } } return KisPaintOpSettings::uniformProperties(settings) + props; } void KisBrushBasedPaintOpSettings::onPropertyChanged() { m_savedBrush.clear(); KisOutlineGenerationPolicy::onPropertyChanged(); } diff --git a/plugins/paintops/libpaintop/kis_curve_option.cpp b/plugins/paintops/libpaintop/kis_curve_option.cpp index 6f93ef2ae3..74479cc67b 100644 --- a/plugins/paintops/libpaintop/kis_curve_option.cpp +++ b/plugins/paintops/libpaintop/kis_curve_option.cpp @@ -1,449 +1,468 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * 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 "kis_curve_option.h" #include KisCurveOption::KisCurveOption(const QString& name, KisPaintOpOption::PaintopCategory category, bool checked, qreal value, qreal min, qreal max) : m_name(name) , m_category(category) , m_checkable(true) , m_checked(checked) , m_useCurve(true) , m_useSameCurve(true) , m_separateCurveValue(false) , m_curveMode(0) { Q_FOREACH (const DynamicSensorType sensorType, KisDynamicSensor::sensorsTypes()) { KisDynamicSensorSP sensor = KisDynamicSensor::type2Sensor(sensorType, m_name); sensor->setActive(false); replaceSensor(sensor); } m_sensorMap[PRESSURE]->setActive(true); setValueRange(min, max); setValue(value); + + QList points; + // needs to be set to something, weird curve is better for debugging + // it will be reset to the curve from the preset anyway though + points.push_back(QPointF(0,0)); + points.push_back(QPointF(0.25,0.9)); + points.push_back(QPointF(0.5,0)); + points.push_back(QPointF(0.75,0.6)); + points.push_back(QPointF(1,0)); + m_commonCurve = KisCubicCurve(points); } KisCurveOption::~KisCurveOption() { } const QString& KisCurveOption::name() const { return m_name; } KisPaintOpOption::PaintopCategory KisCurveOption::category() const { return m_category; } qreal KisCurveOption::minValue() const { return m_minValue; } qreal KisCurveOption::maxValue() const { return m_maxValue; } qreal KisCurveOption::value() const { return m_value; } void KisCurveOption::resetAllSensors() { Q_FOREACH (KisDynamicSensorSP sensor, m_sensorMap.values()) { if (sensor->isActive()) { sensor->reset(); } } } void KisCurveOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { if (m_checkable) { setting->setProperty("Pressure" + m_name, isChecked()); } if (activeSensors().size() == 1) { setting->setProperty(m_name + "Sensor", activeSensors().first()->toXML()); } else { QDomDocument doc = QDomDocument("params"); QDomElement root = doc.createElement("params"); doc.appendChild(root); root.setAttribute("id", "sensorslist"); Q_FOREACH (KisDynamicSensorSP sensor, activeSensors()) { QDomElement childelt = doc.createElement("ChildSensor"); sensor->toXML(doc, childelt); root.appendChild(childelt); } setting->setProperty(m_name + "Sensor", doc.toString()); } setting->setProperty(m_name + "UseCurve", m_useCurve); setting->setProperty(m_name + "UseSameCurve", m_useSameCurve); setting->setProperty(m_name + "Value", m_value); setting->setProperty(m_name + "curveMode", m_curveMode); + setting->setProperty(m_name + "commonCurve", qVariantFromValue(m_commonCurve)); } void KisCurveOption::readOptionSetting(KisPropertiesConfigurationSP setting) { - m_curveCache.clear(); readNamedOptionSetting(m_name, setting); } void KisCurveOption::lodLimitations(KisPaintopLodLimitations *l) const { Q_UNUSED(l); } int KisCurveOption::intMinValue() const { return 0; } int KisCurveOption::intMaxValue() const { return 100; } QString KisCurveOption::valueSuffix() const { return i18n("%"); } void KisCurveOption::readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting) { if (!setting) return; + + KisCubicCurve commonCurve = m_commonCurve; + //dbgKrita << "readNamedOptionSetting" << prefix; // setting->dump(); if (m_checkable) { setChecked(setting->getBool("Pressure" + prefix, false)); } //dbgKrita << "\tPressure" + prefix << isChecked(); m_sensorMap.clear(); // Replace all sensors with the inactive defaults Q_FOREACH (const DynamicSensorType sensorType, KisDynamicSensor::sensorsTypes()) { replaceSensor(KisDynamicSensor::type2Sensor(sensorType, m_name)); } QString sensorDefinition = setting->getString(prefix + "Sensor"); if (!sensorDefinition.contains("sensorslist")) { KisDynamicSensorSP s = KisDynamicSensor::createFromXML(sensorDefinition, m_name); if (s) { replaceSensor(s); s->setActive(true); + commonCurve = s->curve(); //dbgKrita << "\tsingle sensor" << s::id(s->sensorType()) << s->isActive() << "added"; } } else { QDomDocument doc; doc.setContent(sensorDefinition); QDomElement elt = doc.documentElement(); QDomNode node = elt.firstChild(); while (!node.isNull()) { if (node.isElement()) { QDomElement childelt = node.toElement(); if (childelt.tagName() == "ChildSensor") { KisDynamicSensorSP s = KisDynamicSensor::createFromXML(childelt, m_name); if (s) { replaceSensor(s); s->setActive(true); + commonCurve = s->curve(); //dbgKrita << "\tchild sensor" << s::id(s->sensorType()) << s->isActive() << "added"; } } } node = node.nextSibling(); } } + + m_useSameCurve = setting->getBool(m_name + "UseSameCurve", true); + // Only load the old curve format if the curve wasn't saved by the sensor // This will give every sensor the same curve. //dbgKrita << ">>>>>>>>>>>" << prefix + "Sensor" << setting->getString(prefix + "Sensor"); if (!setting->getString(prefix + "Sensor").contains("curve")) { //dbgKrita << "\told format"; if (setting->getBool("Custom" + prefix, false)) { Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) { s->setCurve(setting->getCubicCurve("Curve" + prefix)); + commonCurve = s->curve(); } } } + if (m_useSameCurve) { + m_commonCurve = setting->getCubicCurve(prefix + "commonCurve", commonCurve); + } + // At least one sensor needs to be active if (activeSensors().size() == 0) { m_sensorMap[PRESSURE]->setActive(true); } m_value = setting->getDouble(m_name + "Value", m_maxValue); //dbgKrita << "\t" + m_name + "Value" << m_value; m_useCurve = setting->getBool(m_name + "UseCurve", true); //dbgKrita << "\t" + m_name + "UseCurve" << m_useSameCurve; - m_useSameCurve = setting->getBool(m_name + "UseSameCurve", true); + //dbgKrita << "\t" + m_name + "UseSameCurve" << m_useSameCurve; m_curveMode = setting->getInt(m_name + "curveMode"); //dbgKrita << "-----------------"; } void KisCurveOption::replaceSensor(KisDynamicSensorSP s) { Q_ASSERT(s); m_sensorMap[s->sensorType()] = s; } KisDynamicSensorSP KisCurveOption::sensor(DynamicSensorType sensorType, bool active) const { if (m_sensorMap.contains(sensorType)) { if (!active) { return m_sensorMap[sensorType]; } else { if (m_sensorMap[sensorType]->isActive()) { return m_sensorMap[sensorType]; } } } return 0; } bool KisCurveOption::isRandom() const { return bool(sensor(FUZZY_PER_DAB, true)) || bool(sensor(FUZZY_PER_STROKE, true)); } bool KisCurveOption::isCurveUsed() const { return m_useCurve; } bool KisCurveOption::isSameCurveUsed() const { return m_useSameCurve; } int KisCurveOption::getCurveMode() const { return m_curveMode; } +KisCubicCurve KisCurveOption::getCommonCurve() const +{ + return m_commonCurve; +} + void KisCurveOption::setSeparateCurveValue(bool separateCurveValue) { m_separateCurveValue = separateCurveValue; } bool KisCurveOption::isCheckable() { return m_checkable; } bool KisCurveOption::isChecked() const { return m_checked; } void KisCurveOption::setChecked(bool checked) { m_checked = checked; } void KisCurveOption::setCurveUsed(bool useCurve) { m_useCurve = useCurve; } void KisCurveOption::setCurveMode(int mode) { m_curveMode = mode; } +void KisCurveOption::setUseSameCurve(bool useSameCurve) +{ + m_useSameCurve = useSameCurve; +} + +void KisCurveOption::setCommonCurve(KisCubicCurve curve) +{ + m_commonCurve = curve; +} + void KisCurveOption::setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve) { - // No switch in state, don't mess with the cache if (useSameCurve == m_useSameCurve) { if (useSameCurve) { - Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) { - s->setCurve(curve); - } + m_commonCurve = curve; } else { KisDynamicSensorSP s = sensor(sensorType, false); if (s) { s->setCurve(curve); } } } else { - // moving from not use same curve to use same curve: backup the custom curves if (!m_useSameCurve && useSameCurve) { - // Copy the custom curves to the cache and set the new curve on all sensors, active or not - m_curveCache.clear(); - Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) { - m_curveCache[s->sensorType()] = s->curve(); - s->setCurve(curve); - } + m_commonCurve = curve; } else { //if (m_useSameCurve && !useSameCurve) - // Restore the cached curves KisDynamicSensorSP s = 0; - Q_FOREACH (DynamicSensorType sensorType, m_curveCache.keys()) { - if (m_sensorMap.contains(sensorType)) { - s = m_sensorMap[sensorType]; - } - else { - s = KisDynamicSensor::type2Sensor(sensorType, m_name); - } - s->setCurve(m_curveCache[sensorType]); - m_sensorMap[sensorType] = s; - } - s = 0; // And set the current sensor to the current curve if (!m_sensorMap.contains(sensorType)) { s = KisDynamicSensor::type2Sensor(sensorType, m_name); + } else { + KisDynamicSensorSP s = sensor(sensorType, false); } if (s) { s->setCurve(curve); - s->setCurve(m_curveCache[sensorType]); } } m_useSameCurve = useSameCurve; } } void KisCurveOption::setValueRange(qreal min, qreal max) { m_minValue = qMin(min, max); m_maxValue = qMax(min, max); } void KisCurveOption::setValue(qreal value) { m_value = qBound(m_minValue, value, m_maxValue); } KisCurveOption::ValueComponents KisCurveOption::computeValueComponents(const KisPaintInformation& info) const { ValueComponents components; if (m_useCurve) { QMap::const_iterator i; QList sensorValues; for (i = m_sensorMap.constBegin(); i != m_sensorMap.constEnd(); ++i) { KisDynamicSensorSP s(i.value()); if (s->isActive()) { + qreal valueFromCurve = m_useSameCurve ? s->parameter(info, m_commonCurve, true) : s->parameter(info); if (s->isAdditive()) { - components.additive += s->parameter(info); + components.additive += valueFromCurve; components.hasAdditive = true; } else if (s->isAbsoluteRotation()) { - components.absoluteOffset = s->parameter(info); + components.absoluteOffset = valueFromCurve; components.hasAbsoluteOffset =true; } else { - sensorValues << s->parameter(info); + sensorValues << valueFromCurve; components.hasScaling = true; } } } if (sensorValues.count() == 1) { components.scaling = sensorValues.first(); } else { if (m_curveMode == 1){ // add components.scaling = 0; double i; foreach (i, sensorValues) { components.scaling += i; } } else if (m_curveMode == 2){ //max components.scaling = *std::max_element(sensorValues.begin(), sensorValues.end()); } else if (m_curveMode == 3){ //min components.scaling = *std::min_element(sensorValues.begin(), sensorValues.end()); } else if (m_curveMode == 4){ //difference double max = *std::max_element(sensorValues.begin(), sensorValues.end()); double min = *std::min_element(sensorValues.begin(), sensorValues.end()); components.scaling = max-min; } else { //multuply - default double i; foreach (i, sensorValues) { components.scaling *= i; } } } } if (!m_separateCurveValue) { components.constant = m_value; } components.minSizeLikeValue = m_minValue; components.maxSizeLikeValue = m_maxValue; return components; } qreal KisCurveOption::computeSizeLikeValue(const KisPaintInformation& info) const { const ValueComponents components = computeValueComponents(info); return components.sizeLikeValue(); } qreal KisCurveOption::computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const { const ValueComponents components = computeValueComponents(info); return components.rotationLikeValue(baseValue, absoluteAxesFlipped); } QList KisCurveOption::sensors() { //dbgKrita << "ID" << name() << "has" << m_sensorMap.count() << "Sensors of which" << sensorList.count() << "are active."; return m_sensorMap.values(); } QList KisCurveOption::activeSensors() const { QList sensorList; Q_FOREACH (KisDynamicSensorSP sensor, m_sensorMap.values()) { if (sensor->isActive()) { sensorList << sensor; } } //dbgKrita << "ID" << name() << "has" << m_sensorMap.count() << "Sensors of which" << sensorList.count() << "are active."; return sensorList; } diff --git a/plugins/paintops/libpaintop/kis_curve_option.h b/plugins/paintops/libpaintop/kis_curve_option.h index 881d3dc3a4..22df067944 100644 --- a/plugins/paintops/libpaintop/kis_curve_option.h +++ b/plugins/paintops/libpaintop/kis_curve_option.h @@ -1,213 +1,235 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * 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_CURVE_OPTION_H #define KIS_CURVE_OPTION_H #include #include #include "kis_paintop_option.h" #include "kis_global.h" #include #include "kritapaintop_export.h" #include "kis_dynamic_sensor.h" class KisDynamicSensor; /** * KisCurveOption is the base class for paintop options that are * defined through one or more curves. * * Note: it is NOT a KisPaintOpOption, even though the API is pretty similar! * * KisCurveOption classes have a generic GUI widget, KisCurveOptionWidget. So, * in contrast to KisPaintOpOption classes, KisCurveOption instances can and * will be created in the constructor of KisPaintOp paintops. This class can * manage to read and write its settings directly. * */ class PAINTOP_EXPORT KisCurveOption { public: KisCurveOption(const QString& name, KisPaintOpOption::PaintopCategory category, bool checked, qreal value = 1.0, qreal min = 0.0, qreal max = 1.0); virtual ~KisCurveOption(); virtual void writeOptionSetting(KisPropertiesConfigurationSP setting) const; virtual void readOptionSetting(KisPropertiesConfigurationSP setting); virtual void lodLimitations(KisPaintopLodLimitations *l) const; //Please override for other values than 0-100 and % virtual int intMinValue()const; virtual int intMaxValue()const; virtual QString valueSuffix()const; const QString& name() const; KisPaintOpOption::PaintopCategory category() const; qreal minValue() const; qreal maxValue() const; qreal value() const; void resetAllSensors(); KisDynamicSensorSP sensor(DynamicSensorType sensorType, bool active) const; void replaceSensor(KisDynamicSensorSP sensor); QList sensors(); QList activeSensors() const; bool isCheckable(); bool isChecked() const; bool isCurveUsed() const; bool isSameCurveUsed() const; bool isRandom() const; int getCurveMode() const; + /** + * Returns the curve that is being used instead of sensor ones + * in case "Use the same curve" is checked. + */ + KisCubicCurve getCommonCurve() const; + void setSeparateCurveValue(bool separateCurveValue); void setChecked(bool checked); void setCurveUsed(bool useCurve); void setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve); void setValue(qreal value); void setCurveMode(int mode); + /** + * Sets the bool indicating whether "Share curve across all settings" is checked. + */ + void setUseSameCurve(bool useSameCurve); + + /** + * Sets the curve that is being used instead of sensor ones + * in case "Share curve across all settings" is checked. + */ + void setCommonCurve(KisCubicCurve curve); + struct ValueComponents { ValueComponents() : constant(1.0), scaling(1.0), additive(0.0), absoluteOffset(0.0), hasAbsoluteOffset(false), hasScaling(false), hasAdditive(false) { } qreal constant; qreal scaling; qreal additive; qreal absoluteOffset; bool hasAbsoluteOffset; bool hasScaling; bool hasAdditive; qreal minSizeLikeValue; qreal maxSizeLikeValue; /** * @param normalizedBaseAngle canvas rotation angle normalized to range [0; 1] * @param absoluteAxesFlipped true if underlying image coordinate system is flipped (horiz. mirror != vert. mirror) */ qreal rotationLikeValue(qreal normalizedBaseAngle, bool absoluteAxesFlipped) const { const qreal offset = !hasAbsoluteOffset ? normalizedBaseAngle : absoluteAxesFlipped ? 1.0 - absoluteOffset : absoluteOffset; const qreal realScalingPart = hasScaling ? KisDynamicSensor::scalingToAdditive(scaling) : 0.0; const qreal realAdditivePart = hasAdditive ? additive : 0; qreal value = wrapInRange(2 * offset + constant * (realScalingPart + realAdditivePart), -1.0, 1.0); if (qIsNaN(value)) { qWarning() << "rotationLikeValue returns NaN!" << normalizedBaseAngle << absoluteAxesFlipped; value = 0; } return value; } qreal sizeLikeValue() const { const qreal offset = hasAbsoluteOffset ? absoluteOffset : 1.0; const qreal realScalingPart = hasScaling ? scaling : 1.0; const qreal realAdditivePart = hasAdditive ? KisDynamicSensor::additiveToScaling(additive) : 1.0; return qBound(minSizeLikeValue, constant * offset * realScalingPart * realAdditivePart, maxSizeLikeValue); } private: static inline qreal wrapInRange(qreal x, qreal min, qreal max) { const qreal range = max - min; x -= min; if (x < 0.0) { x = range + fmod(x, range); } if (x > range) { x = fmod(x, range); } return x + min; } }; /** * Uses the curves set on the sensors to compute a single * double value that can control the parameters of a brush. * * This value is derives from the values stored in * ValuesComponents object. */ ValueComponents computeValueComponents(const KisPaintInformation& info) const; qreal computeSizeLikeValue(const KisPaintInformation &info) const; qreal computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const; protected: void setValueRange(qreal min, qreal max); /** * Read the option using the prefix in argument */ void readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting); QString m_name; KisPaintOpOption::PaintopCategory m_category; bool m_checkable; bool m_checked; bool m_useCurve; bool m_useSameCurve; bool m_separateCurveValue; + /** + * Curve that is being used instead of sensors' internal ones + * in case "Use the same curve" is checked. + */ + KisCubicCurve m_commonCurve; + int m_curveMode; QMap m_sensorMap; - QMap m_curveCache; private: qreal m_value; qreal m_minValue; qreal m_maxValue; }; #endif diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp index 66d41decdb..52be81b200 100644 --- a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp +++ b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp @@ -1,340 +1,360 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * * 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 "kis_curve_option_widget.h" #include "ui_wdgcurveoption.h" #include "widgets/kis_curve_widget.h" #include "kis_dynamic_sensor.h" #include "kis_global.h" #include "kis_curve_option.h" #include "kis_signals_blocker.h" #include "kis_icon_utils.h" inline void setLabel(QLabel* label, const KisCurveLabel& curve_label) { if (curve_label.icon().isNull()) { label->setText(curve_label.name()); } else { label->setPixmap(QPixmap::fromImage(curve_label.icon())); } } KisCurveOptionWidget::KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider) : KisPaintOpOption(curveOption->category(), curveOption->isChecked()) , m_widget(new QWidget) , m_curveOptionWidget(new Ui_WdgCurveOption()) , m_curveOption(curveOption) { setObjectName("KisCurveOptionWidget"); m_curveOptionWidget->setupUi(m_widget); setConfigurationPage(m_widget); m_curveOptionWidget->sensorSelector->setCurveOption(curveOption); updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); connect(m_curveOptionWidget->curveWidget, SIGNAL(modified()), this, SLOT(slotModified())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(emitSettingChanged())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(updateLabelsOfCurrentSensor())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateSensorCurveLabels(KisDynamicSensorSP))); connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateCurve(KisDynamicSensorSP))); - connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(slotStateChanged())); + connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(slotUseSameCurveChanged())); // set all the icons for the curve preset shapes updateThemedIcons(); // various curve preset buttons with predefined curves connect(m_curveOptionWidget->linearCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLinear())); connect(m_curveOptionWidget->revLinearButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseLinear())); connect(m_curveOptionWidget->jCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveJShape())); connect(m_curveOptionWidget->lCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLShape())); connect(m_curveOptionWidget->sCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveSShape())); connect(m_curveOptionWidget->reverseSCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseSShape())); connect(m_curveOptionWidget->uCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveUShape())); connect(m_curveOptionWidget->revUCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveArchShape())); m_curveOptionWidget->label_ymin->setText(minLabel); m_curveOptionWidget->label_ymax->setText(maxLabel); // strength settings is shown as 0-100% m_curveOptionWidget->strengthSlider->setRange(curveOption->minValue()*100, curveOption->maxValue()*100, 0); m_curveOptionWidget->strengthSlider->setValue(curveOption->value()*100); m_curveOptionWidget->strengthSlider->setPrefix(i18n("Strength: ")); m_curveOptionWidget->strengthSlider->setSuffix(i18n("%")); if (hideSlider) { m_curveOptionWidget->strengthSlider->hide(); } connect(m_curveOptionWidget->checkBoxUseCurve, SIGNAL(stateChanged(int)) , SLOT(updateValues())); connect(m_curveOptionWidget->curveMode, SIGNAL(currentIndexChanged(int)), SLOT(updateMode())); connect(m_curveOptionWidget->strengthSlider, SIGNAL(valueChanged(qreal)), SLOT(updateValues())); } KisCurveOptionWidget::~KisCurveOptionWidget() { delete m_curveOption; delete m_curveOptionWidget; } void KisCurveOptionWidget::writeOptionSetting(KisPropertiesConfigurationSP setting) const { m_curveOption->writeOptionSetting(setting); } void KisCurveOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { //setting->dump(); m_curveOption->readOptionSetting(setting); + // Signals needs to be blocked, otherwise checking the checkbox will trigger + // setting the common curve to the widget curve, which is incorrect in this case. + bool blockedBefore = m_curveOptionWidget->checkBoxUseSameCurve->blockSignals(true); + m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed()); + m_curveOptionWidget->checkBoxUseSameCurve->blockSignals(blockedBefore); + m_curveOptionWidget->checkBoxUseCurve->setChecked(m_curveOption->isCurveUsed()); m_curveOptionWidget->strengthSlider->setValue(m_curveOption->value()*100); - m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed()); m_curveOptionWidget->curveMode->setCurrentIndex(m_curveOption->getCurveMode()); disableWidgets(!m_curveOption->isCurveUsed()); m_curveOptionWidget->sensorSelector->reload(); m_curveOptionWidget->sensorSelector->setCurrent(m_curveOption->activeSensors().first()); updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); - - if (m_curveOption->isSameCurveUsed()) { - // make sure the curve is transfered to all sensors to avoid updating from a wrong curve later - transferCurve(); - } } void KisCurveOptionWidget::lodLimitations(KisPaintopLodLimitations *l) const { m_curveOption->lodLimitations(l); } bool KisCurveOptionWidget::isCheckable() const { return m_curveOption->isCheckable(); } bool KisCurveOptionWidget::isChecked() const { return m_curveOption->isChecked(); } void KisCurveOptionWidget::setChecked(bool checked) { m_curveOption->setChecked(checked); } KisCurveOption* KisCurveOptionWidget::curveOption() { return m_curveOption; } QWidget* KisCurveOptionWidget::curveWidget() { return m_widget; } void KisCurveOptionWidget::slotModified() { - transferCurve(); -} - -void KisCurveOptionWidget::slotStateChanged() -{ - transferCurve(); + if (!m_curveOption->isSameCurveUsed()) { + m_curveOptionWidget->sensorSelector->currentHighlighted()->setCurve(getWidgetCurve()); + } else { + m_curveOption->setCommonCurve(getWidgetCurve()); + } + emitSettingChanged(); } - -void KisCurveOptionWidget::transferCurve() +void KisCurveOptionWidget::slotUseSameCurveChanged() { - m_curveOptionWidget->sensorSelector->setCurrentCurve(m_curveOptionWidget->curveWidget->curve(), m_curveOptionWidget->checkBoxUseSameCurve->isChecked()); + // this is a slot that answers on "Share Curve across all settings" checkbox + m_curveOption->setUseSameCurve(m_curveOptionWidget->checkBoxUseSameCurve->isChecked()); + if (m_curveOption->isSameCurveUsed()) { + // !(UseSameCurve) => UseSameCurve + // set the current curve to the common curve + m_curveOption->setCommonCurve(getWidgetCurve()); + } else { + updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); + } emitSettingChanged(); } void KisCurveOptionWidget::updateSensorCurveLabels(KisDynamicSensorSP sensor) { if (sensor) { m_curveOptionWidget->label_xmin->setText(KisDynamicSensor::minimumLabel(sensor->sensorType())); m_curveOptionWidget->label_xmax->setText(KisDynamicSensor::maximumLabel(sensor->sensorType(), sensor->length())); int inMinValue = KisDynamicSensor::minimumValue(sensor->sensorType()); int inMaxValue = KisDynamicSensor::maximumValue(sensor->sensorType(), sensor->length()); QString inSuffix = KisDynamicSensor::valueSuffix(sensor->sensorType()); int outMinValue = m_curveOption->intMinValue(); int outMaxValue = m_curveOption->intMaxValue(); QString outSuffix = m_curveOption->valueSuffix(); m_curveOptionWidget->intIn->setSuffix(inSuffix); m_curveOptionWidget->intOut->setSuffix(outSuffix); m_curveOptionWidget->curveWidget->setupInOutControls(m_curveOptionWidget->intIn,m_curveOptionWidget->intOut, inMinValue,inMaxValue,outMinValue,outMaxValue); } } void KisCurveOptionWidget::updateCurve(KisDynamicSensorSP sensor) { if (sensor) { bool blockSignal = m_curveOptionWidget->curveWidget->blockSignals(true); - m_curveOptionWidget->curveWidget->setCurve(sensor->curve()); + KisCubicCurve curve = m_curveOption->isSameCurveUsed() ? m_curveOption->getCommonCurve() : sensor->curve(); + m_curveOptionWidget->curveWidget->setCurve(curve); m_curveOptionWidget->curveWidget->blockSignals(blockSignal); } } void KisCurveOptionWidget::updateLabelsOfCurrentSensor() { updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); } void KisCurveOptionWidget::updateValues() { m_curveOption->setValue(m_curveOptionWidget->strengthSlider->value()/100.0); // convert back to 0-1 for data m_curveOption->setCurveUsed(m_curveOptionWidget->checkBoxUseCurve->isChecked()); disableWidgets(!m_curveOptionWidget->checkBoxUseCurve->isChecked()); emitSettingChanged(); } void KisCurveOptionWidget::updateMode() { m_curveOption->setCurveMode(m_curveOptionWidget->curveMode->currentIndex()); emitSettingChanged(); } void KisCurveOptionWidget::changeCurveLinear() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveReverseLinear() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveSShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.25,0.1)); points.push_back(QPointF(0.75,0.9)); points.push_back(QPointF(1, 1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveReverseSShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.25,0.9)); points.push_back(QPointF(0.75,0.1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveJShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.35,0.1)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveLShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.25,0.48)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveUShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.5,0)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveArchShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.5,1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::disableWidgets(bool disable) { m_curveOptionWidget->checkBoxUseSameCurve->setDisabled(disable); m_curveOptionWidget->curveWidget->setDisabled(disable); m_curveOptionWidget->sensorSelector->setDisabled(disable); m_curveOptionWidget->label_xmax->setDisabled(disable); m_curveOptionWidget->label_xmin->setDisabled(disable); m_curveOptionWidget->label_ymax->setDisabled(disable); m_curveOptionWidget->label_ymin->setDisabled(disable); } void KisCurveOptionWidget::updateThemedIcons() { // set all the icons for the curve preset shapes m_curveOptionWidget->linearCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear")); m_curveOptionWidget->revLinearButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear-reverse")); m_curveOptionWidget->jCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-j")); m_curveOptionWidget->lCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-l")); m_curveOptionWidget->sCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s")); m_curveOptionWidget->reverseSCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s-reverse")); m_curveOptionWidget->uCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-u")); m_curveOptionWidget->revUCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-arch")); // this helps make the checkboxes show themselves on the dark color themes QPalette pal = m_curveOptionWidget->sensorSelector->palette(); QPalette newPalette = pal; newPalette.setColor(QPalette::Active, QPalette::Background, pal.text().color() ); m_curveOptionWidget->sensorSelector->setPalette(newPalette); } + + + +KisCubicCurve KisCurveOptionWidget::getWidgetCurve() +{ + return m_curveOptionWidget->curveWidget->curve(); +} + +KisCubicCurve KisCurveOptionWidget::getHighlightedSensorCurve() +{ + return m_curveOptionWidget->sensorSelector->currentHighlighted()->curve(); +} diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.h b/plugins/paintops/libpaintop/kis_curve_option_widget.h index e5ba4a2ccc..5c2396c1e5 100644 --- a/plugins/paintops/libpaintop/kis_curve_option_widget.h +++ b/plugins/paintops/libpaintop/kis_curve_option_widget.h @@ -1,87 +1,90 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * * 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_CURVE_OPTION_WIDGET_H #define KIS_CURVE_OPTION_WIDGET_H #include class Ui_WdgCurveOption; class KisCurveOption; class QComboBox; #include class PAINTOP_EXPORT KisCurveOptionWidget : public KisPaintOpOption { Q_OBJECT public: KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider = false); ~KisCurveOptionWidget() override; void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void lodLimitations(KisPaintopLodLimitations *l) const override; bool isCheckable() const override; bool isChecked() const override; void setChecked(bool checked) override; void show(); protected: KisCurveOption* curveOption(); QWidget* curveWidget(); private Q_SLOTS: void slotModified(); - void slotStateChanged(); + void slotUseSameCurveChanged(); - void transferCurve(); void updateSensorCurveLabels(KisDynamicSensorSP sensor); void updateCurve(KisDynamicSensorSP sensor); void updateValues(); void updateMode(); void updateLabelsOfCurrentSensor(); void disableWidgets(bool disable); void updateThemedIcons(); // curve shape preset buttons void changeCurveLinear(); void changeCurveReverseLinear(); void changeCurveSShape(); void changeCurveReverseSShape(); void changeCurveJShape(); void changeCurveLShape(); void changeCurveUShape(); void changeCurveArchShape(); private: QWidget* m_widget; Ui_WdgCurveOption* m_curveOptionWidget; QComboBox* m_curveMode; KisCurveOption* m_curveOption; + + KisCubicCurve getWidgetCurve(); + KisCubicCurve getHighlightedSensorCurve(); + }; #endif // KIS_CURVE_OPTION_WIDGET_H diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc index 0c56495f77..fb167c7918 100644 --- a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc +++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc @@ -1,576 +1,581 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * 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 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dynamic_sensor.h" #include #include "kis_algebra_2d.h" #include "sensors/kis_dynamic_sensors.h" #include "sensors/kis_dynamic_sensor_distance.h" #include "sensors/kis_dynamic_sensor_drawing_angle.h" #include "sensors/kis_dynamic_sensor_time.h" #include "sensors/kis_dynamic_sensor_fade.h" #include "sensors/kis_dynamic_sensor_fuzzy.h" KisDynamicSensor::KisDynamicSensor(DynamicSensorType type) : m_length(-1) , m_type(type) , m_customCurve(false) , m_active(false) { } KisDynamicSensor::~KisDynamicSensor() { } QWidget* KisDynamicSensor::createConfigurationWidget(QWidget* parent, QWidget*) { Q_UNUSED(parent); return 0; } void KisDynamicSensor::reset() { } KisDynamicSensorSP KisDynamicSensor::id2Sensor(const KoID& id, const QString &parentOptionName) { if (id.id() == PressureId.id()) { return new KisDynamicSensorPressure(); } else if (id.id() == PressureInId.id()) { return new KisDynamicSensorPressureIn(); } else if (id.id() == XTiltId.id()) { return new KisDynamicSensorXTilt(); } else if (id.id() == YTiltId.id()) { return new KisDynamicSensorYTilt(); } else if (id.id() == TiltDirectionId.id()) { return new KisDynamicSensorTiltDirection(); } else if (id.id() == TiltElevationId.id()) { return new KisDynamicSensorTiltElevation(); } else if (id.id() == SpeedId.id()) { return new KisDynamicSensorSpeed(); } else if (id.id() == DrawingAngleId.id()) { return new KisDynamicSensorDrawingAngle(); } else if (id.id() == RotationId.id()) { return new KisDynamicSensorRotation(); } else if (id.id() == DistanceId.id()) { return new KisDynamicSensorDistance(); } else if (id.id() == TimeId.id()) { return new KisDynamicSensorTime(); } else if (id.id() == FuzzyPerDabId.id()) { return new KisDynamicSensorFuzzy(false, parentOptionName); } else if (id.id() == FuzzyPerStrokeId.id()) { return new KisDynamicSensorFuzzy(true, parentOptionName); } else if (id.id() == FadeId.id()) { return new KisDynamicSensorFade(); } else if (id.id() == PerspectiveId.id()) { return new KisDynamicSensorPerspective(); } else if (id.id() == TangentialPressureId.id()) { return new KisDynamicSensorTangentialPressure(); } dbgPlugins << "Unknown transform parameter :" << id.id(); return 0; } DynamicSensorType KisDynamicSensor::id2Type(const KoID &id) { if (id.id() == PressureId.id()) { return PRESSURE; } else if (id.id() == PressureInId.id()) { return PRESSURE_IN; } else if (id.id() == XTiltId.id()) { return XTILT; } else if (id.id() == YTiltId.id()) { return YTILT; } else if (id.id() == TiltDirectionId.id()) { return TILT_DIRECTION; } else if (id.id() == TiltElevationId.id()) { return TILT_ELEVATATION; } else if (id.id() == SpeedId.id()) { return SPEED; } else if (id.id() == DrawingAngleId.id()) { return ANGLE; } else if (id.id() == RotationId.id()) { return ROTATION; } else if (id.id() == DistanceId.id()) { return DISTANCE; } else if (id.id() == TimeId.id()) { return TIME; } else if (id.id() == FuzzyPerDabId.id()) { return FUZZY_PER_DAB; } else if (id.id() == FuzzyPerStrokeId.id()) { return FUZZY_PER_STROKE; } else if (id.id() == FadeId.id()) { return FADE; } else if (id.id() == PerspectiveId.id()) { return PERSPECTIVE; } else if (id.id() == TangentialPressureId.id()) { return TANGENTIAL_PRESSURE; } return UNKNOWN; } KisDynamicSensorSP KisDynamicSensor::type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName) { switch (sensorType) { case FUZZY_PER_DAB: return new KisDynamicSensorFuzzy(false, parentOptionName); case FUZZY_PER_STROKE: return new KisDynamicSensorFuzzy(true, parentOptionName); case SPEED: return new KisDynamicSensorSpeed(); case FADE: return new KisDynamicSensorFade(); case DISTANCE: return new KisDynamicSensorDistance(); case TIME: return new KisDynamicSensorTime(); case ANGLE: return new KisDynamicSensorDrawingAngle(); case ROTATION: return new KisDynamicSensorRotation(); case PRESSURE: return new KisDynamicSensorPressure(); case XTILT: return new KisDynamicSensorXTilt(); case YTILT: return new KisDynamicSensorYTilt(); case TILT_DIRECTION: return new KisDynamicSensorTiltDirection(); case TILT_ELEVATATION: return new KisDynamicSensorTiltElevation(); case PERSPECTIVE: return new KisDynamicSensorPerspective(); case TANGENTIAL_PRESSURE: return new KisDynamicSensorTangentialPressure(); case PRESSURE_IN: return new KisDynamicSensorPressureIn(); default: return 0; } } QString KisDynamicSensor::minimumLabel(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: return QString(); case FADE: return i18n("0"); case DISTANCE: return i18n("0 px"); case TIME: return i18n("0 s"); case ANGLE: return i18n("0°"); case SPEED: return i18n("Slow"); case ROTATION: return i18n("0°"); case PRESSURE: return i18n("Low"); case XTILT: return i18n("-30°"); case YTILT: return i18n("-30°"); case TILT_DIRECTION: return i18n("0°"); case TILT_ELEVATATION: return i18n("90°"); case PERSPECTIVE: return i18n("Far"); case TANGENTIAL_PRESSURE: case PRESSURE_IN: return i18n("Low"); default: return i18n("0.0"); } } QString KisDynamicSensor::maximumLabel(DynamicSensorType sensorType, int max) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: return QString(); case FADE: if (max < 0) return i18n("1000"); else return i18n("%1", max); case DISTANCE: if (max < 0) return i18n("30 px"); else return i18n("%1 px", max); case TIME: if (max < 0) return i18n("3 s"); else return i18n("%1 s", max / 1000); case ANGLE: return i18n("360°"); case SPEED: return i18n("Fast"); case ROTATION: return i18n("360°"); case PRESSURE: return i18n("High"); case XTILT: return i18n("30°"); case YTILT: return i18n("30°"); case TILT_DIRECTION: return i18n("360°"); case TILT_ELEVATATION: return i18n("0°"); case PERSPECTIVE: return i18n("Near"); case TANGENTIAL_PRESSURE: case PRESSURE_IN: return i18n("High"); default: return i18n("1.0"); }; } int KisDynamicSensor::minimumValue(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: case FADE: case DISTANCE: case TIME: case ANGLE: case SPEED: case ROTATION: case PRESSURE: case TILT_DIRECTION: case PERSPECTIVE: case PRESSURE_IN: return 0; case XTILT: case YTILT: return -30; case TILT_ELEVATATION: return 90; case TANGENTIAL_PRESSURE: default: return 0; } } int KisDynamicSensor::maximumValue(DynamicSensorType sensorType, int max) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: case SPEED: case PERSPECTIVE: case TANGENTIAL_PRESSURE: case PRESSURE_IN: case PRESSURE: return 100; case FADE: if (max < 0) { return 1000; } else { return max; } case DISTANCE: if (max < 0) { return 30; } else { return max; } case TIME: if (max < 0) { return 3000; } else { return max; } case ANGLE: case ROTATION: case TILT_DIRECTION: return 360; case XTILT: case YTILT: return 30; case TILT_ELEVATATION: return 0; default: return 100; }; } QString KisDynamicSensor::valueSuffix(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: case SPEED: case PRESSURE: case PERSPECTIVE: case TANGENTIAL_PRESSURE: case PRESSURE_IN: return i18n("%"); case FADE: return QString(); case DISTANCE: return i18n(" px"); case TIME: return i18n(" ms"); case ANGLE: case ROTATION: case XTILT: case YTILT: case TILT_DIRECTION: case TILT_ELEVATATION: return i18n("°"); default: return i18n("%"); }; } KisDynamicSensorSP KisDynamicSensor::createFromXML(const QString& s, const QString &parentOptionName) { QDomDocument doc; doc.setContent(s); QDomElement e = doc.documentElement(); return createFromXML(e, parentOptionName); } KisDynamicSensorSP KisDynamicSensor::createFromXML(const QDomElement& e, const QString &parentOptionName) { QString id = e.attribute("id", ""); KisDynamicSensorSP sensor = id2Sensor(id, parentOptionName); if (sensor) { sensor->fromXML(e); } return sensor; } QList KisDynamicSensor::sensorsIds() { QList ids; ids << PressureId << PressureInId << XTiltId << YTiltId << TiltDirectionId << TiltElevationId << SpeedId << DrawingAngleId << RotationId << DistanceId << TimeId << FuzzyPerDabId << FuzzyPerStrokeId << FadeId << PerspectiveId << TangentialPressureId; return ids; } QList KisDynamicSensor::sensorsTypes() { QList sensorTypes; sensorTypes << PRESSURE << PRESSURE_IN << XTILT << YTILT << TILT_DIRECTION << TILT_ELEVATATION << SPEED << ANGLE << ROTATION << DISTANCE << TIME << FUZZY_PER_DAB << FUZZY_PER_STROKE << FADE << PERSPECTIVE << TANGENTIAL_PRESSURE; return sensorTypes; } QString KisDynamicSensor::id(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: return "fuzzy"; case FUZZY_PER_STROKE: return "fuzzystroke"; case FADE: return "fade"; case DISTANCE: return "distance"; case TIME: return "time"; case ANGLE: return "drawingangle"; case SPEED: return "speed"; case ROTATION: return "rotation"; case PRESSURE: return "pressure"; case XTILT: return "xtilt"; case YTILT: return "ytilt"; case TILT_DIRECTION: return "ascension"; case TILT_ELEVATATION: return "declination"; case PERSPECTIVE: return "perspective"; case TANGENTIAL_PRESSURE: return "tangentialpressure"; case PRESSURE_IN: return "pressurein"; case SENSORS_LIST: return "sensorslist"; default: return QString(); }; } void KisDynamicSensor::toXML(QDomDocument& doc, QDomElement& elt) const { elt.setAttribute("id", id(sensorType())); if (m_customCurve) { QDomElement curve_elt = doc.createElement("curve"); QDomText text = doc.createTextNode(m_curve.toString()); curve_elt.appendChild(text); elt.appendChild(curve_elt); } } void KisDynamicSensor::fromXML(const QDomElement& e) { Q_ASSERT(e.attribute("id", "") == id(sensorType())); m_customCurve = false; QDomElement curve_elt = e.firstChildElement("curve"); if (!curve_elt.isNull()) { m_customCurve = true; m_curve.fromString(curve_elt.text()); } } qreal KisDynamicSensor::parameter(const KisPaintInformation& info) +{ + return parameter(info, m_curve, m_customCurve); +} + +qreal KisDynamicSensor::parameter(const KisPaintInformation& info, const KisCubicCurve curve, const bool customCurve) { const qreal val = value(info); - if (m_customCurve) { + if (customCurve) { qreal scaledVal = isAdditive() ? additiveToScaling(val) : val; - const QVector transfer = m_curve.floatTransfer(256); + const QVector transfer = curve.floatTransfer(256); scaledVal = KisCubicCurve::interpolateLinear(scaledVal, transfer); return isAdditive() ? scalingToAdditive(scaledVal) : scaledVal; } else { return val; } } void KisDynamicSensor::setCurve(const KisCubicCurve& curve) { m_customCurve = true; m_curve = curve; } const KisCubicCurve& KisDynamicSensor::curve() const { return m_curve; } void KisDynamicSensor::removeCurve() { m_customCurve = false; } bool KisDynamicSensor::hasCustomCurve() const { return m_customCurve; } bool KisDynamicSensor::dependsOnCanvasRotation() const { return true; } bool KisDynamicSensor::isAdditive() const { return false; } bool KisDynamicSensor::isAbsoluteRotation() const { return false; } void KisDynamicSensor::setActive(bool active) { m_active = active; } bool KisDynamicSensor::isActive() const { return m_active; } diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.h b/plugins/paintops/libpaintop/kis_dynamic_sensor.h index b349822eda..e06e98ea2f 100644 --- a/plugins/paintops/libpaintop/kis_dynamic_sensor.h +++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.h @@ -1,222 +1,229 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * 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 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_DYNAMIC_SENSOR_H_ #define _KIS_DYNAMIC_SENSOR_H_ #include #include #include #include #include "kis_serializable_configuration.h" #include "kis_curve_label.h" #include #include #include class QWidget; class KisPaintInformation; const KoID FuzzyPerDabId("fuzzy", ki18n("Fuzzy Dab")); ///< generate a random number const KoID FuzzyPerStrokeId("fuzzystroke", ki18n("Fuzzy Stroke")); ///< generate a random number const KoID SpeedId("speed", ki18n("Speed")); ///< generate a number depending on the speed of the cursor const KoID FadeId("fade", ki18n("Fade")); ///< generate a number that increase every time you call it (e.g. per dab) const KoID DistanceId("distance", ki18n("Distance")); ///< generate a number that increase with distance const KoID TimeId("time", ki18n("Time")); ///< generate a number that increase with time const KoID DrawingAngleId("drawingangle", ki18n("Drawing angle")); ///< number depending on the angle const KoID RotationId("rotation", ki18n("Rotation")); ///< rotation coming from the device const KoID PressureId("pressure", ki18n("Pressure")); ///< number depending on the pressure const KoID PressureInId("pressurein", ki18n("PressureIn")); ///< number depending on the pressure const KoID XTiltId("xtilt", ki18n("X-Tilt")); ///< number depending on X-tilt const KoID YTiltId("ytilt", ki18n("Y-Tilt")); ///< number depending on Y-tilt /** * "TiltDirection" and "TiltElevation" parameters are written to * preset files as "ascension" and "declination" to keep backward * compatibility with older presets from the days when they were called * differently. */ const KoID TiltDirectionId("ascension", ki18n("Tilt direction")); /// < number depending on the X and Y tilt, tilt direction is 0 when stylus nib points to you and changes clockwise from -180 to +180. const KoID TiltElevationId("declination", ki18n("Tilt elevation")); /// < tilt elevation is 90 when stylus is perpendicular to tablet and 0 when it's parallel to tablet const KoID PerspectiveId("perspective", ki18n("Perspective")); ///< number depending on the distance on the perspective grid const KoID TangentialPressureId("tangentialpressure", ki18n("Tangential pressure")); ///< the wheel on an airbrush device const KoID SensorsListId("sensorslist", "SHOULD NOT APPEAR IN THE UI !"); ///< this a non user-visible sensor that can store a list of other sensors, and multiply their output class KisDynamicSensor; typedef KisSharedPtr KisDynamicSensorSP; enum DynamicSensorType { FUZZY_PER_DAB, FUZZY_PER_STROKE, SPEED, FADE, DISTANCE, TIME, ANGLE, ROTATION, PRESSURE, XTILT, YTILT, TILT_DIRECTION, TILT_ELEVATATION, PERSPECTIVE, TANGENTIAL_PRESSURE, SENSORS_LIST, PRESSURE_IN, UNKNOWN = 255 }; /** * Sensors are used to extract from KisPaintInformation a single * double value which can be used to control the parameters of * a brush. */ class PAINTOP_EXPORT KisDynamicSensor : public KisSerializableConfiguration { public: enum ParameterSign { NegativeParameter = -1, UnSignedParameter = 0, PositiveParameter = 1 }; protected: KisDynamicSensor(DynamicSensorType type); public: ~KisDynamicSensor() override; /** * @return the value of this sensor for the given KisPaintInformation */ qreal parameter(const KisPaintInformation& info); + /** + * @return the value of this sensor for the given KisPaintInformation + * curve -- a custom, temporary curve that should be used instead of the one for the sensor + * customCurve -- if it's a new curve or not; should always be true if the function is called from outside + * (aka not in parameter(info) function) + */ + qreal parameter(const KisPaintInformation& info, const KisCubicCurve curve, const bool customCurve); /** * This function is call before beginning a stroke to reset the sensor. * Default implementation does nothing. */ virtual void reset(); /** * @param parent the parent QWidget * @param selector is a \ref QWidget that contains a signal called "parametersChanged()" */ virtual QWidget* createConfigurationWidget(QWidget* parent, QWidget* selector); /** * Creates a sensor from its identifier. */ static KisDynamicSensorSP id2Sensor(const KoID& id, const QString &parentOptionName); static KisDynamicSensorSP id2Sensor(const QString& s, const QString &parentOptionName) { return id2Sensor(KoID(s), parentOptionName); } static DynamicSensorType id2Type(const KoID& id); static DynamicSensorType id2Type(const QString& s) { return id2Type(KoID(s)); } /** * type2Sensor creates a new sensor for the give type */ static KisDynamicSensorSP type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName); static QString minimumLabel(DynamicSensorType sensorType); static QString maximumLabel(DynamicSensorType sensorType, int max = -1); static int minimumValue(DynamicSensorType sensorType); static int maximumValue(DynamicSensorType sensorType, int max = -1); static QString valueSuffix(DynamicSensorType sensorType); static KisDynamicSensorSP createFromXML(const QString&, const QString &parentOptionName); static KisDynamicSensorSP createFromXML(const QDomElement&, const QString &parentOptionName); /** * @return the list of sensors */ static QList sensorsIds(); static QList sensorsTypes(); /** * @return the identifier of this sensor */ static QString id(DynamicSensorType sensorType); using KisSerializableConfiguration::fromXML; using KisSerializableConfiguration::toXML; void toXML(QDomDocument&, QDomElement&) const override; void fromXML(const QDomElement&) override; void setCurve(const KisCubicCurve& curve); const KisCubicCurve& curve() const; void removeCurve(); bool hasCustomCurve() const; void setActive(bool active); bool isActive() const; virtual bool dependsOnCanvasRotation() const; virtual bool isAdditive() const; virtual bool isAbsoluteRotation() const; inline DynamicSensorType sensorType() const { return m_type; } /** * @return the currently set length or -1 if not relevant */ int length() { return m_length; } public: static inline qreal scalingToAdditive(qreal x) { return -1.0 + 2.0 * x; } static inline qreal additiveToScaling(qreal x) { return 0.5 * (1.0 + x); } protected: virtual qreal value(const KisPaintInformation& info) = 0; int m_length; private: Q_DISABLE_COPY(KisDynamicSensor) DynamicSensorType m_type; bool m_customCurve; KisCubicCurve m_curve; bool m_active; }; #endif diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc index 9e2747f23c..a905497b3e 100644 --- a/plugins/tools/basictools/kis_tool_move.cc +++ b/plugins/tools/basictools/kis_tool_move.cc @@ -1,714 +1,714 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2002 Patrick Julien * 2004 Boudewijn Rempt * 2016 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_move.h" #include #include "kis_cursor.h" #include "kis_selection.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_tool_utils.h" #include "kis_paint_layer.h" #include "strokes/move_stroke_strategy.h" #include "kis_tool_movetooloptionswidget.h" #include "strokes/move_selection_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_action_registry.h" #include "krita_utils.h" #include #include #include "kis_node_manager.h" #include "kis_selection_manager.h" #include "kis_signals_blocker.h" #include #include "KisMoveBoundsCalculationJob.h" struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable { KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {} KisToolChangesTrackerData* clone() const override { return new KisToolMoveState(*this); } bool operator ==(const KisToolMoveState &rhs) { return accumulatedOffset == rhs.accumulatedOffset; } QPoint accumulatedOffset; }; KisToolMove::KisToolMove(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::moveCursor()) , m_updateCursorCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { setObjectName("tool_move"); m_showCoordinatesAction = action("movetool-show-coordinates"); m_showCoordinatesAction = action("movetool-show-coordinates"); connect(&m_updateCursorCompressor, SIGNAL(timeout()), this, SLOT(resetCursorStyle())); m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId()); // 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_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates()); m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset()); connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges()), Qt::UniqueConnection); connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint)), Qt::UniqueConnection); connect(qobject_cast(canvas)->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList)), Qt::UniqueConnection); connect(qobject_cast(canvas)->viewManager()->selectionManager(), SIGNAL(currentSelectionChanged()), this, SLOT(slotSelectionChanged()), Qt::UniqueConnection); } KisToolMove::~KisToolMove() { endStroke(); } void KisToolMove::resetCursorStyle() { KisTool::resetCursorStyle(); if (!isActive()) return; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisNodeList nodes = fetchSelectedNodes(moveToolMode(), &m_lastCursorPos, selection); if (nodes.isEmpty()) { canvas()->setCursor(Qt::ForbiddenCursor); } } KisNodeList KisToolMove::fetchSelectedNodes(MoveToolMode mode, const QPoint *pixelPoint, KisSelectionSP selection) { KisNodeList nodes; KisImageSP image = this->image(); if (mode != MoveSelectedLayer && pixelPoint) { const bool wholeGroup = !selection && mode == MoveGroup; KisNodeSP node = KisToolUtils::findNode(image->root(), *pixelPoint, wholeGroup); if (node) { nodes = {node}; } } if (nodes.isEmpty()) { nodes = this->selectedNodes(); KritaUtils::filterContainer(nodes, [](KisNodeSP node) { return node->isEditable(); }); } return nodes; } bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos) { KisNodeSP node; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisNodeList nodes = fetchSelectedNodes(mode, pos, selection); if (nodes.size() == 1) { node = nodes.first(); } if (nodes.isEmpty()) { return false; } /** * If the target node has changed, the stroke should be * restarted. Otherwise just continue processing current node. */ if (m_strokeId && !tryEndPreviousStroke(nodes)) { return true; } KisStrokeStrategy *strategy; KisPaintLayerSP paintLayer = node ? dynamic_cast(node.data()) : 0; bool isMoveSelection = false; if (paintLayer && selection && (!selection->selectedRect().isEmpty() && !selection->selectedExactRect().isEmpty())) { MoveSelectionStrokeStrategy *moveStrategy = new MoveSelectionStrokeStrategy(paintLayer, selection, image.data(), image.data()); connect(moveStrategy, SIGNAL(sigHandlesRectCalculated(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect&))); strategy = moveStrategy; isMoveSelection = true; } else { MoveStrokeStrategy *moveStrategy = new MoveStrokeStrategy(nodes, image.data(), image.data()); connect(moveStrategy, SIGNAL(sigHandlesRectCalculated(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect&))); strategy = moveStrategy; } // disable outline feedback until the stroke calcualtes // correct bounding rect m_handlesRect = QRect(); m_strokeId = image->startStroke(strategy); m_currentlyProcessingNodes = nodes; m_accumulatedOffset = QPoint(); if (!isMoveSelection) { m_asyncUpdateHelper.startUpdateStream(image.data(), m_strokeId); } KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) { m_changesTracker.reset(); } commitChanges(); return true; } QPoint KisToolMove::currentOffset() const { return m_accumulatedOffset + m_dragPos - m_dragStart; } void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage) { if (!m_optionsWidget) return; if (m_handlesRect.isEmpty()) return; const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset(); KisSignalsBlocker b(m_optionsWidget); emit moveInNewPosition(currentTopLeft); // TODO: fetch this info not from options widget, but from config const bool showCoordinates = m_optionsWidget->showCoordinates(); if (showCoordinates && showFloatingMessage) { - KisCanvas2 *kisCanvas = dynamic_cast(canvas()); + KisCanvas2 *kisCanvas = static_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in move tool", "X: %1 px, Y: %2 px", QLocale().toString(currentTopLeft.x()), QLocale().toString(currentTopLeft.y())), QIcon(), 1000, KisFloatingMessage::High); } } bool KisToolMove::tryEndPreviousStroke(const KisNodeList &nodes) { if (!m_strokeId) return false; bool strokeEnded = false; if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { endStroke(); strokeEnded = true; } return strokeEnded; } void KisToolMove::commitChanges() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); QSharedPointer newState(new KisToolMoveState(m_accumulatedOffset)); KisToolMoveState *lastState = dynamic_cast(m_changesTracker.lastState().data()); if (lastState && *lastState == *newState) return; m_changesTracker.commitConfig(newState); } void KisToolMove::slotHandlesRectCalculated(const QRect &handlesRect) { m_handlesRect = handlesRect; notifyGuiAfterMove(false); } void KisToolMove::moveDiscrete(MoveDirection direction, bool big) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()) return; if (!image()) return; if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } // Larger movement if "shift" key is pressed. qreal scale = big ? m_optionsWidget->moveScale() : 1.0; qreal moveStep = m_optionsWidget->moveStep() * scale; const QPoint offset = direction == Up ? QPoint( 0, -moveStep) : direction == Down ? QPoint( 0, moveStep) : direction == Left ? QPoint(-moveStep, 0) : QPoint( moveStep, 0) ; m_accumulatedOffset += offset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); commitChanges(); setMode(KisTool::HOVER_MODE); } void KisToolMove::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUp())); m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDown())); m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeft())); m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRight())); m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUpMore())); m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDownMore())); m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeftMore())); m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRightMore())); connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool)), Qt::UniqueConnection); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); slotNodeChanged(this->selectedNodes()); } void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (m_strokeId && !m_handlesRect.isEmpty()) { QPainterPath handles; handles.addRect(m_handlesRect.translated(currentOffset())); QPainterPath path = pixelToView(handles); paintToolOutline(&gc, path); } } void KisToolMove::deactivate() { m_actionConnections.clear(); disconnect(m_showCoordinatesAction, 0, this, 0); disconnect(m_optionsWidget, 0, this, 0); endStroke(); KisTool::deactivate(); } void KisToolMove::requestStrokeEnd() { endStroke(); } void KisToolMove::requestStrokeCancellation() { cancelStroke(); } void KisToolMove::requestUndoDuringStroke() { if (!m_strokeId) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolMove::beginPrimaryAction(KoPointerEvent *event) { startAction(event, moveToolMode()); } void KisToolMove::continuePrimaryAction(KoPointerEvent *event) { continueAction(event); } void KisToolMove::endPrimaryAction(KoPointerEvent *event) { endAction(event); } void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { // Ctrl+Right click toggles between moving current layer and moving layer w/ content if (action == PickFgNode || action == PickBgImage) { MoveToolMode mode = moveToolMode(); if (mode == MoveSelectedLayer) { mode = MoveFirstLayer; } else if (mode == MoveFirstLayer) { mode = MoveSelectedLayer; } startAction(event, mode); } else { startAction(event, MoveGroup); } } void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) continueAction(event); } void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) endAction(event); } void KisToolMove::mouseMoveEvent(KoPointerEvent *event) { m_lastCursorPos = convertToPixelCoord(event).toPoint(); KisTool::mouseMoveEvent(event); if (moveToolMode() == MoveFirstLayer) { m_updateCursorCompressor.start(); } } void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode) { QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); m_dragStart = pos; m_dragPos = pos; if (startStrokeImpl(mode, &pos)) { setMode(KisTool::PAINT_MODE); } else { event->ignore(); m_dragPos = QPoint(); m_dragStart = QPoint(); } qobject_cast(canvas())->updateCanvas(); } void KisToolMove::continueAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); m_dragPos = pos; drag(pos); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::endAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); drag(pos); m_accumulatedOffset += pos - m_dragStart; m_dragStart = QPoint(); m_dragPos = QPoint(); commitChanges(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::drag(const QPoint& newPos) { KisImageWSP image = currentImage(); QPoint offset = m_accumulatedOffset + newPos - m_dragStart; image->addJob(m_strokeId, new MoveStrokeStrategy::Data(offset)); } void KisToolMove::endStroke() { if (!m_strokeId) return; if (m_asyncUpdateHelper.isActive()) { m_asyncUpdateHelper.endUpdateStream(); } KisImageSP image = currentImage(); image->endStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_accumulatedOffset = QPoint(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); KisToolMoveState *newState = dynamic_cast(state.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newState); if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging m_accumulatedOffset = newState->accumulatedOffset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); } void KisToolMove::slotMoveDiscreteLeft() { moveDiscrete(MoveDirection::Left, false); } void KisToolMove::slotMoveDiscreteRight() { moveDiscrete(MoveDirection::Right, false); } void KisToolMove::slotMoveDiscreteUp() { moveDiscrete(MoveDirection::Up, false); } void KisToolMove::slotMoveDiscreteDown() { moveDiscrete(MoveDirection::Down, false); } void KisToolMove::slotMoveDiscreteLeftMore() { moveDiscrete(MoveDirection::Left, true); } void KisToolMove::slotMoveDiscreteRightMore() { moveDiscrete(MoveDirection::Right, true); } void KisToolMove::slotMoveDiscreteUpMore() { moveDiscrete(MoveDirection::Up, true); } void KisToolMove::slotMoveDiscreteDownMore() { moveDiscrete(MoveDirection::Down, true); } void KisToolMove::cancelStroke() { if (!m_strokeId) return; if (m_asyncUpdateHelper.isActive()) { m_asyncUpdateHelper.cancelUpdateStream(); } KisImageSP image = currentImage(); image->cancelStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_accumulatedOffset = QPoint(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } QWidget* KisToolMove::createOptionWidget() { return m_optionsWidget; } KisToolMove::MoveToolMode KisToolMove::moveToolMode() const { if (m_optionsWidget) return m_optionsWidget->mode(); return MoveSelectedLayer; } QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos) { QPoint move = pos - m_dragStart; // Snap to axis if (modifiers & Qt::ShiftModifier) { move = snapToClosestAxis(move); } // "Precision mode" - scale down movement by 1/5 if (modifiers & Qt::AltModifier) { const qreal SCALE_FACTOR = .2; move = SCALE_FACTOR * move; } return m_dragStart + move; } void KisToolMove::moveBySpinX(int newX) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.rx() = newX - m_handlesRect.x(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::moveBySpinY(int newY) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.ry() = newY - m_handlesRect.y(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::requestHandlesRectUpdate() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisMoveBoundsCalculationJob *job = new KisMoveBoundsCalculationJob(this->selectedNodes(), selection, this); connect(job, SIGNAL(sigCalcualtionFinished(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect &))); KisImageSP image = this->image(); image->addSpontaneousJob(job); notifyGuiAfterMove(false); } void KisToolMove::slotNodeChanged(const KisNodeList &nodes) { if (m_strokeId && !tryEndPreviousStroke(nodes)) { return; } requestHandlesRectUpdate(); } void KisToolMove::slotSelectionChanged() { if (m_strokeId) return; requestHandlesRectUpdate(); } QList KisToolMoveFactory::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("movetool-move-up"); actions << actionRegistry->makeQAction("movetool-move-down"); actions << actionRegistry->makeQAction("movetool-move-left"); actions << actionRegistry->makeQAction("movetool-move-right"); actions << actionRegistry->makeQAction("movetool-move-up-more"); actions << actionRegistry->makeQAction("movetool-move-down-more"); actions << actionRegistry->makeQAction("movetool-move-left-more"); actions << actionRegistry->makeQAction("movetool-move-right-more"); actions << actionRegistry->makeQAction("movetool-show-coordinates"); return actions; } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp index 58582f866c..a92e804783 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp @@ -1,340 +1,350 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_lazy_brush.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_config.h" #include "kundo2magicstring.h" #include "KoProperties.h" #include "kis_node_manager.h" #include "kis_layer_properties_icons.h" #include "kis_canvas_resource_provider.h" #include "kis_tool_lazy_brush_options_widget.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_signal_auto_connection.h" struct KisToolLazyBrush::Private { bool activateMaskMode = false; bool oldShowKeyStrokesValue = false; bool oldShowColoringValue = false; - KisNodeSP manuallyActivatedNode; + KisNodeWSP manuallyActivatedNode; KisSignalAutoConnectionsStore toolConnections; }; KisToolLazyBrush::KisToolLazyBrush(KoCanvasBase * canvas) : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.png", 5, 5), kundo2_i18n("Colorize Mask Key Stroke")), m_d(new Private) { setObjectName("tool_lazybrush"); } KisToolLazyBrush::~KisToolLazyBrush() { } void KisToolLazyBrush::tryDisableKeyStrokesOnMask() { - if (m_d->manuallyActivatedNode) { - KisLayerPropertiesIcons::setNodeProperty(m_d->manuallyActivatedNode, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image()); - m_d->manuallyActivatedNode = 0; + // upgrade to strong pointer + KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; + + if (manuallyActivatedNode) { + KisLayerPropertiesIcons::setNodeProperty(manuallyActivatedNode, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image()); + manuallyActivatedNode = 0; } + + m_d->manuallyActivatedNode = 0; } void KisToolLazyBrush::activate(ToolActivation activation, const QSet &shapes) { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); m_d->toolConnections.addUniqueConnection( kiscanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); KisColorizeMask *mask = qobject_cast(currentNode().data()); if (mask) { mask->regeneratePrefilteredDeviceIfNeeded(); } KisToolFreehand::activate(activation, shapes); } void KisToolLazyBrush::deactivate() { KisToolFreehand::deactivate(); tryDisableKeyStrokesOnMask(); m_d->toolConnections.clear(); } void KisToolLazyBrush::slotCurrentNodeChanged(KisNodeSP node) { - if (node != m_d->manuallyActivatedNode) { + // upgrade to strong pointer + KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; + + if (node != manuallyActivatedNode) { tryDisableKeyStrokesOnMask(); KisColorizeMask *mask = qobject_cast(node.data()); if (mask) { mask->regeneratePrefilteredDeviceIfNeeded(); } } } void KisToolLazyBrush::resetCursorStyle() { KisToolFreehand::resetCursorStyle(); } bool KisToolLazyBrush::colorizeMaskActive() const { KisNodeSP node = currentNode(); return node && node->inherits("KisColorizeMask"); } bool KisToolLazyBrush::canCreateColorizeMask() const { KisNodeSP node = currentNode(); return node && node->inherits("KisLayer"); } bool KisToolLazyBrush::shouldActivateKeyStrokes() const { KisNodeSP node = currentNode(); return node && node->inherits("KisColorizeMask") && !KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); } void KisToolLazyBrush::tryCreateColorizeMask() { KisNodeSP node = currentNode(); if (!node) return; KoProperties properties; properties.setProperty("visible", true); properties.setProperty("locked", false); QList masks = node->childNodes(QStringList("KisColorizeMask"), properties); if (!masks.isEmpty()) { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->nodeManager()->slotNonUiActivatedNode(masks.first()); } else { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->nodeManager()->createNode("KisColorizeMask"); } } void KisToolLazyBrush::activatePrimaryAction() { KisToolFreehand::activatePrimaryAction(); if (shouldActivateKeyStrokes() || (!colorizeMaskActive() && canCreateColorizeMask())) { useCursor(KisCursor::handCursor()); m_d->activateMaskMode = true; setOutlineEnabled(false); } } void KisToolLazyBrush::deactivatePrimaryAction() { if (m_d->activateMaskMode) { m_d->activateMaskMode = false; setOutlineEnabled(true); resetCursorStyle(); } KisToolFreehand::deactivatePrimaryAction(); } void KisToolLazyBrush::beginPrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) { if (!colorizeMaskActive() && canCreateColorizeMask()) { tryCreateColorizeMask(); } else if (shouldActivateKeyStrokes()) { + // upgrade to strong pointer + KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; KisNodeSP node = currentNode(); - KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->manuallyActivatedNode || - m_d->manuallyActivatedNode == node); + KIS_SAFE_ASSERT_RECOVER_NOOP(!manuallyActivatedNode || + manuallyActivatedNode == node); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true, image()); m_d->manuallyActivatedNode = node; } } else { KisToolFreehand::beginPrimaryAction(event); } } void KisToolLazyBrush::continuePrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) return; KisToolFreehand::continuePrimaryAction(event); } void KisToolLazyBrush::endPrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) return; KisToolFreehand::endPrimaryAction(event); } void KisToolLazyBrush::activateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::Secondary && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; m_d->oldShowKeyStrokesValue = KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, !m_d->oldShowKeyStrokesValue, image()); KisToolFreehand::activatePrimaryAction(); } else if (action == KisTool::Third && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; m_d->oldShowColoringValue = KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, !m_d->oldShowColoringValue, image()); KisToolFreehand::activatePrimaryAction(); } else { KisToolFreehand::activateAlternateAction(action); } } void KisToolLazyBrush::deactivateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::Secondary && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, m_d->oldShowKeyStrokesValue, image()); KisToolFreehand::deactivatePrimaryAction(); } else if (action == KisTool::Third && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, m_d->oldShowColoringValue, image()); KisToolFreehand::deactivatePrimaryAction(); } else { KisToolFreehand::deactivateAlternateAction(action); } } void KisToolLazyBrush::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { beginPrimaryAction(event); } else { KisToolFreehand::beginAlternateAction(event, action); } } void KisToolLazyBrush::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { continuePrimaryAction(event); } else { KisToolFreehand::continueAlternateAction(event, action); } } void KisToolLazyBrush::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { endPrimaryAction(event); } else { KisToolFreehand::endAlternateAction(event, action); } } void KisToolLazyBrush::explicitUserStrokeEndRequest() { if (m_d->activateMaskMode) { tryCreateColorizeMask(); } else if (colorizeMaskActive()) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, image()); } } QWidget * KisToolLazyBrush::createOptionWidget() { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); QWidget *optionsWidget = new KisToolLazyBrushOptionsWidget(kiscanvas->viewManager()->canvasResourceProvider(), 0); optionsWidget->setObjectName(toolId() + "option widget"); // // 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); return optionsWidget; }