├── .gitignore ├── .idx └── dev.nix ├── .vs └── KritaColorPlus │ └── v17 │ └── DocumentLayout.backup.json ├── .vscode └── settings.json ├── LICENSE ├── README-italian.md ├── README.md ├── actions ├── cleanupLayers.action ├── cycle_next_brush.action ├── decrease_layer_opacity.action ├── dry_paper.action ├── dry_paper_and_pick_color.action ├── increase_layer_opacity.action ├── last_color.action ├── mix_color_because_mistake.action ├── mix_color_because_want_to_fadeout.action ├── pick_color.action ├── reset_layer_opacity_to_default.action ├── toggle_auto_mixing.action ├── toggle_dirty_brush.action └── view_single_window.action ├── install_gui.py └── pykrita ├── recent_color.desktop └── recent_color ├── TODO.md ├── __init__.py ├── __pycache__ ├── __init__.cpython-38.pyc └── recent_color.cpython-38.pyc ├── brush_cycler.py ├── brush_list_widget.py ├── color_history_docker.py ├── globals.py ├── mainDocker.py ├── mergeAllPythonFiles.py ├── mouseMonitor.py ├── recent_color.py ├── rgb.py ├── slider.py ├── spectral.py └── whichtool.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs/KritaColorPlus/CopilotIndices/17.13.441.19478/CodeChunks.db 2 | /.vs/KritaColorPlus/CopilotIndices/17.13.441.19478/SemanticSymbols.db 3 | /.vs/KritaColorPlus/FileContentIndex/64abcc15-2bfd-43e7-b5f5-cd69eba7e0dc.vsidx 4 | /.vs/KritaColorPlus/FileContentIndex/e94a2356-29d4-49f4-b2f0-627bb4bd96ed.vsidx 5 | /.vs/KritaColorPlus/copilot-chat/273f3cdd/sessions/263d166f-a071-4c09-87cf-e18026511443 6 | /.vs/KritaColorPlus/copilot-chat/273f3cdd/sessions/708b2f09-7cb3-4799-8f73-640bb94c4c21 7 | /.vs/KritaColorPlus/v17/.wsuo 8 | /.vs/KritaColorPlus/v17/DocumentLayout.json 9 | /.vs/VSWorkspaceState.json 10 | /.vs/slnx.sqlite 11 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-console-l1-1-0.dll 12 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-console-l1-2-0.dll 13 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-datetime-l1-1-0.dll 14 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-debug-l1-1-0.dll 15 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-errorhandling-l1-1-0.dll 16 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-fibers-l1-1-0.dll 17 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-file-l1-1-0.dll 18 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-file-l1-2-0.dll 19 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-file-l2-1-0.dll 20 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-handle-l1-1-0.dll 21 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-heap-l1-1-0.dll 22 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-interlocked-l1-1-0.dll 23 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-libraryloader-l1-1-0.dll 24 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-localization-l1-2-0.dll 25 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-memory-l1-1-0.dll 26 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-namedpipe-l1-1-0.dll 27 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-processenvironment-l1-1-0.dll 28 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-processthreads-l1-1-0.dll 29 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-processthreads-l1-1-1.dll 30 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-profile-l1-1-0.dll 31 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-rtlsupport-l1-1-0.dll 32 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-string-l1-1-0.dll 33 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-synch-l1-1-0.dll 34 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-synch-l1-2-0.dll 35 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-sysinfo-l1-1-0.dll 36 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-timezone-l1-1-0.dll 37 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-core-util-l1-1-0.dll 38 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-conio-l1-1-0.dll 39 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-convert-l1-1-0.dll 40 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-environment-l1-1-0.dll 41 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-filesystem-l1-1-0.dll 42 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-heap-l1-1-0.dll 43 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-locale-l1-1-0.dll 44 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-math-l1-1-0.dll 45 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-multibyte-l1-1-0.dll 46 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-private-l1-1-0.dll 47 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-process-l1-1-0.dll 48 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-runtime-l1-1-0.dll 49 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-stdio-l1-1-0.dll 50 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-string-l1-1-0.dll 51 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-time-l1-1-0.dll 52 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/api-ms-win-crt-utility-l1-1-0.dll 53 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/clretwrc.dll 54 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/clrjit.dll 55 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/coreclr.dll 56 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/createdump.exe 57 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/dbgshim.dll 58 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/hostfxr.dll 59 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/hostpolicy.dll 60 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/KritaMouseHook.deps.json 61 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/KritaMouseHook.dll 62 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/KritaMouseHook.exe 63 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/KritaMouseHook.pdb 64 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/KritaMouseHook.runtimeconfig.json 65 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/Microsoft.CSharp.dll 66 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/Microsoft.DiaSymReader.Native.amd64.dll 67 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/Microsoft.VisualBasic.Core.dll 68 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/Microsoft.VisualBasic.dll 69 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/Microsoft.Win32.Primitives.dll 70 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/Microsoft.Win32.Registry.dll 71 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/mscordaccore_amd64_amd64_6.0.3624.51421.dll 72 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/mscordaccore.dll 73 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/mscordbi.dll 74 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/mscorlib.dll 75 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/mscorrc.dll 76 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/msquic.dll 77 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/netstandard.dll 78 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.AppContext.dll 79 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Buffers.dll 80 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Collections.Concurrent.dll 81 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Collections.dll 82 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Collections.Immutable.dll 83 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Collections.NonGeneric.dll 84 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Collections.Specialized.dll 85 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ComponentModel.Annotations.dll 86 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ComponentModel.DataAnnotations.dll 87 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ComponentModel.dll 88 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ComponentModel.EventBasedAsync.dll 89 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ComponentModel.Primitives.dll 90 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ComponentModel.TypeConverter.dll 91 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Configuration.dll 92 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Console.dll 93 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Core.dll 94 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Data.Common.dll 95 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Data.DataSetExtensions.dll 96 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Data.dll 97 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.Contracts.dll 98 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.Debug.dll 99 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.DiagnosticSource.dll 100 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.FileVersionInfo.dll 101 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.Process.dll 102 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.StackTrace.dll 103 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.TextWriterTraceListener.dll 104 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.Tools.dll 105 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.TraceSource.dll 106 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Diagnostics.Tracing.dll 107 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.dll 108 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Drawing.dll 109 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Drawing.Primitives.dll 110 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Dynamic.Runtime.dll 111 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Formats.Asn1.dll 112 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Globalization.Calendars.dll 113 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Globalization.dll 114 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Globalization.Extensions.dll 115 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Compression.Brotli.dll 116 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Compression.dll 117 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Compression.FileSystem.dll 118 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Compression.Native.dll 119 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Compression.ZipFile.dll 120 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.dll 121 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.FileSystem.AccessControl.dll 122 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.FileSystem.dll 123 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.FileSystem.DriveInfo.dll 124 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.FileSystem.Primitives.dll 125 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.FileSystem.Watcher.dll 126 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.IsolatedStorage.dll 127 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.MemoryMappedFiles.dll 128 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Pipes.AccessControl.dll 129 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.Pipes.dll 130 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.IO.UnmanagedMemoryStream.dll 131 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Linq.dll 132 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Linq.Expressions.dll 133 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Linq.Parallel.dll 134 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Linq.Queryable.dll 135 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Memory.dll 136 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.dll 137 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Http.dll 138 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Http.Json.dll 139 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.HttpListener.dll 140 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Mail.dll 141 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.NameResolution.dll 142 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.NetworkInformation.dll 143 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Ping.dll 144 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Primitives.dll 145 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Quic.dll 146 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Requests.dll 147 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Security.dll 148 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.ServicePoint.dll 149 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.Sockets.dll 150 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.WebClient.dll 151 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.WebHeaderCollection.dll 152 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.WebProxy.dll 153 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.WebSockets.Client.dll 154 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Net.WebSockets.dll 155 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Numerics.dll 156 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Numerics.Vectors.dll 157 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ObjectModel.dll 158 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Private.CoreLib.dll 159 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Private.DataContractSerialization.dll 160 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Private.Uri.dll 161 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Private.Xml.dll 162 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Private.Xml.Linq.dll 163 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.DispatchProxy.dll 164 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.dll 165 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.Emit.dll 166 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.Emit.ILGeneration.dll 167 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.Emit.Lightweight.dll 168 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.Extensions.dll 169 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.Metadata.dll 170 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.Primitives.dll 171 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Reflection.TypeExtensions.dll 172 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Resources.Reader.dll 173 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Resources.ResourceManager.dll 174 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Resources.Writer.dll 175 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.CompilerServices.Unsafe.dll 176 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.CompilerServices.VisualC.dll 177 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.dll 178 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Extensions.dll 179 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Handles.dll 180 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.InteropServices.dll 181 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.InteropServices.RuntimeInformation.dll 182 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Intrinsics.dll 183 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Loader.dll 184 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Numerics.dll 185 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Serialization.dll 186 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Serialization.Formatters.dll 187 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Serialization.Json.dll 188 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Serialization.Primitives.dll 189 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Runtime.Serialization.Xml.dll 190 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.AccessControl.dll 191 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Claims.dll 192 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.Algorithms.dll 193 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.Cng.dll 194 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.Csp.dll 195 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.Encoding.dll 196 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.OpenSsl.dll 197 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.Primitives.dll 198 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Cryptography.X509Certificates.dll 199 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.dll 200 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Principal.dll 201 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.Principal.Windows.dll 202 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Security.SecureString.dll 203 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ServiceModel.Web.dll 204 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ServiceProcess.dll 205 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Text.Encoding.CodePages.dll 206 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Text.Encoding.dll 207 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Text.Encoding.Extensions.dll 208 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Text.Encodings.Web.dll 209 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Text.Json.dll 210 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Text.RegularExpressions.dll 211 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Channels.dll 212 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.dll 213 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Overlapped.dll 214 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Tasks.Dataflow.dll 215 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Tasks.dll 216 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Tasks.Extensions.dll 217 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Tasks.Parallel.dll 218 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Thread.dll 219 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.ThreadPool.dll 220 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Threading.Timer.dll 221 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Transactions.dll 222 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Transactions.Local.dll 223 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.ValueTuple.dll 224 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Web.dll 225 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Web.HttpUtility.dll 226 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Windows.dll 227 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.dll 228 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.Linq.dll 229 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.ReaderWriter.dll 230 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.Serialization.dll 231 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.XDocument.dll 232 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.XmlDocument.dll 233 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.XmlSerializer.dll 234 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.XPath.dll 235 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/System.Xml.XPath.XDocument.dll 236 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/ucrtbase.dll 237 | KritaMouseHookCS/bin/Debug/net6.0-windows/win-x64/WindowsBase.dll 238 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/apphost.exe 239 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMou.EE1D15FB.Up2Date 240 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMouseHook.csproj.CoreCompileInputs.cache 241 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMouseHook.csproj.FileListAbsolute.txt 242 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMouseHook.dll 243 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMouseHook.genruntimeconfig.cache 244 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMouseHook.pdb 245 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/KritaMouseHook.sourcelink.json 246 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/ref/KritaMouseHook.dll 247 | KritaMouseHookCS/obj/Debug/net6.0-windows/win-x64/refint/KritaMouseHook.dll 248 | pykrita/recent_color/recent_color - Copy.txt 249 | .vs/KritaColorPlus/CopilotIndices/17.13.441.19478/CodeChunks.db-shm 250 | .vs/KritaColorPlus/CopilotIndices/17.13.441.19478/CodeChunks.db-wal 251 | .vs/KritaColorPlus/CopilotIndices/17.13.441.19478/SemanticSymbols.db-shm 252 | .vs/KritaColorPlus/CopilotIndices/17.13.441.19478/SemanticSymbols.db-wal 253 | .vs/KritaColorPlus/FileContentIndex/2d43a268-11ed-4a81-aaf9-b4edd2b21f28.vsidx 254 | .vs/KritaColorPlus/FileContentIndex/7e9d8827-1b59-44df-8bbc-97797565092e.vsidx 255 | .vs/KritaColorPlus/FileContentIndex/e428ff13-e654-4b09-a42e-f6ff0d9a874a.vsidx 256 | .vs/KritaColorPlus/FileContentIndex/ac193e7d-21bc-4bcb-807b-781635bbce53.vsidx 257 | /.vs/KritaColorPlus/FileContentIndex 258 | -------------------------------------------------------------------------------- /.idx/dev.nix: -------------------------------------------------------------------------------- 1 | # To learn more about how to use Nix to configure your environment 2 | # see: https://firebase.google.com/docs/studio/customize-workspace 3 | { pkgs, ... }: { 4 | # Which nixpkgs channel to use. 5 | channel = "stable-24.05"; # or "unstable" 6 | 7 | # Use https://search.nixos.org/packages to find packages 8 | packages = [ 9 | # pkgs.go 10 | # pkgs.python311 11 | # pkgs.python311Packages.pip 12 | # pkgs.nodejs_20 13 | # pkgs.nodePackages.nodemon 14 | ]; 15 | 16 | # Sets environment variables in the workspace 17 | env = {}; 18 | idx = { 19 | # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" 20 | extensions = [ 21 | # "vscodevim.vim" 22 | ]; 23 | 24 | # Enable previews 25 | previews = { 26 | enable = true; 27 | previews = { 28 | # web = { 29 | # # Example: run "npm run dev" with PORT set to IDX's defined port for previews, 30 | # # and show it in IDX's web preview panel 31 | # command = ["npm" "run" "dev"]; 32 | # manager = "web"; 33 | # env = { 34 | # # Environment variables to set for your server 35 | # PORT = "$PORT"; 36 | # }; 37 | # }; 38 | }; 39 | }; 40 | 41 | # Workspace lifecycle hooks 42 | workspace = { 43 | # Runs when a workspace is first created 44 | onCreate = { 45 | # Example: install JS dependencies from NPM 46 | # npm-install = "npm install"; 47 | }; 48 | # Runs when the workspace is (re)started 49 | onStart = { 50 | # Example: start a background task to watch and re-build backend code 51 | # watch-backend = "npm run watch-backend"; 52 | }; 53 | }; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /.vs/KritaColorPlus/v17/DocumentLayout.backup.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 1, 3 | "WorkspaceRootPath": "C:\\Users\\Maurizio\\Software\\KritaColorPlus\\", 4 | "Documents": [ 5 | { 6 | "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Maurizio\\Software\\KritaColorPlus\\pykrita\\recent_color\\recent_color.py||{8B382828-6202-11D1-8870-0000F87579D2}", 7 | "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:pykrita\\recent_color\\recent_color.py||{8B382828-6202-11D1-8870-0000F87579D2}" 8 | } 9 | ], 10 | "DocumentGroupContainers": [ 11 | { 12 | "Orientation": 0, 13 | "VerticalTabListWidth": 256, 14 | "DocumentGroups": [ 15 | { 16 | "DockedWidth": 200, 17 | "SelectedChildIndex": 12, 18 | "Children": [ 19 | { 20 | "$type": "Bookmark", 21 | "Name": "ST:3:0:{e8b06f52-6d01-11d2-aa7d-00c04f990343}" 22 | }, 23 | { 24 | "$type": "Bookmark", 25 | "Name": "ST:2:0:{e8b06f52-6d01-11d2-aa7d-00c04f990343}" 26 | }, 27 | { 28 | "$type": "Bookmark", 29 | "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}" 30 | }, 31 | { 32 | "$type": "Bookmark", 33 | "Name": "ST:0:0:{a80febb4-e7e0-4147-b476-21aaf2453969}" 34 | }, 35 | { 36 | "$type": "Bookmark", 37 | "Name": "ST:0:0:{a0c5197d-0ac7-4b63-97cd-8872a789d233}" 38 | }, 39 | { 40 | "$type": "Bookmark", 41 | "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" 42 | }, 43 | { 44 | "$type": "Bookmark", 45 | "Name": "ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}" 46 | }, 47 | { 48 | "$type": "Bookmark", 49 | "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" 50 | }, 51 | { 52 | "$type": "Bookmark", 53 | "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" 54 | }, 55 | { 56 | "$type": "Bookmark", 57 | "Name": "ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}" 58 | }, 59 | { 60 | "$type": "Bookmark", 61 | "Name": "ST:0:0:{40ea2e6b-2121-4bb8-a43e-c83c04b51041}" 62 | }, 63 | { 64 | "$type": "Bookmark", 65 | "Name": "ST:128:0:{13b12e3e-c1b4-4539-9371-4fe9a0d523fc}" 66 | }, 67 | { 68 | "$type": "Document", 69 | "DocumentIndex": 0, 70 | "Title": "recent_color.py", 71 | "DocumentMoniker": "C:\\Users\\Maurizio\\Software\\KritaColorPlus\\pykrita\\recent_color\\recent_color.py", 72 | "RelativeDocumentMoniker": "pykrita\\recent_color\\recent_color.py", 73 | "ToolTip": "C:\\Users\\Maurizio\\Software\\KritaColorPlus\\pykrita\\recent_color\\recent_color.py", 74 | "RelativeToolTip": "pykrita\\recent_color\\recent_color.py", 75 | "ViewState": "AgIAADQLAAAAAAAAAAAAADQLAAAIAAAAAAAAAA==", 76 | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", 77 | "WhenOpened": "2025-04-06T13:07:11.454Z", 78 | "EditorCaption": "" 79 | }, 80 | { 81 | "$type": "Bookmark", 82 | "Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}" 83 | } 84 | ] 85 | }, 86 | { 87 | "DockedWidth": 200, 88 | "SelectedChildIndex": -1, 89 | "Children": [ 90 | { 91 | "$type": "Bookmark", 92 | "Name": "ST:0:0:{b1e99781-ab81-11d0-b683-00aa00a3ee26}" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IDX.aI.enableInlineCompletion": true, 3 | "IDX.aI.enableCodebaseIndexing": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README-italian.md: -------------------------------------------------------------------------------- 1 | # Indice 2 | 3 | 1. [ColorPlus](#colorplus) 4 | 2. [Funzionalità](#funzionalità) 5 | * [Logica spectral del color mixing](#logica-spectral-del-color-mixing) 6 | * [Auto-mix](#auto-mix) 7 | * [Dirty brush](#dirty-brush) 8 | * [Alternanza rapida tra colori](#alternanza-rapida-tra-colori) 9 | * [Color picker intelligente](#color-picker-intelligente) 10 | * [Color Mixing shortcut](#color-mixing-shortcut) 11 | * [Post-correzione del colore](#post-correzione-del-colore) 12 | * [Modificare la trasparenza della pennellata](#modificare-la-trasparenza-della-pennellata) 13 | * [Simulazione di acquerello](#simulazione-di-acquerello) 14 | * [Color history intelligente](#color-history-intelligente) 15 | * [Docker principale](#docker-principale) 16 | * [Preview a tutto schermo](#preview-a-tutto-schermo) 17 | * [Salvataggio e ripristino delle finestre e della loro posizione](#salvataggio-e-ripristino-delle-finestre-e-della-loro-posizione) 18 | * [Esportazione di layer e coordinate](#esportazione-di-layer-e-coordinate) 19 | * [Autofocus delle finestre](#autofocus-delle-finestre) 20 | 3. [INSTALLAZIONE](#installazione) 21 | * [In Windows](#in-windows) 22 | 23 | # ColorPlus 24 | 25 | ColorPlus è un plugin per Krita che aggiunge a Krita le seguenti funzionalità: 26 | 27 | # Funzionalità 28 | 29 | ## Logica spectral del color mixing 30 | 31 | Questo significa che i colori si mixano in modo realistico. Ad esempio, quando mixi due colori, 32 | il giallo mixato con blu produce verde; blu con bianco produce un blu acceso e vibrante; ecc. 33 | 34 | Questo mixaggio realistico avviene in tutte le feature di ColorPlus: 35 | mixing manuale, auto-mix, dirty brush. 36 | 37 | Grazie a Ronald van Wijnen per la libreria utilizzata per il mixaggio 38 | dei colori secondo la teoria Kubelka-Munk! 39 | 40 | ## Auto-mix 41 | 42 | Se attivi questa modalità, ogni tua pennellata si sporca automaticamente con il colore che 43 | è già presente sulla tela. La proporzione in cui si sporca è configurabile con uno slider. 44 | 45 | 46 | Ogni pennellata si sporca indipendentemente. Cioè, ad ogni pennellata, il pennello viene prima 47 | "auto-pulito", poi sporcato col nuovo colore sulla tela. (Se invece vuoi che il pennello resti 48 | sporco tra le varie pennellate, puoi usare il dirty brush. Vedi sezione apposita). 49 | 50 | Quindi, ad ogni pennellata introdurrete un po' di colore. Continuando a pennellare, 51 | alla fine arriverà al colore target. (A patto che il mixing radius sia abbastanza 52 | basso, altrimenti non ci arriverete mai). 53 | 54 | Differenze col Color Smudge Engine di Krita: 55 | 56 | Il Color Smudge di Krita mixa i colori senza logica spectral, quindi produce 57 | colori sbiaditi: ad esempio, blu + giallo = grigio, non verde. 58 | 59 | Il Color Smudge Engine di Krita funziona con il concetto di "color rate", cioè il tuo colore 60 | scelto (foreground color) viene introdotto gradualmente durante la pennellata. Non puoi avere 61 | una pennellata che sin dall'inizio ha il tuo foreground color, ma sporcato di una percentuale 62 | fissa col colore sulla tela. Questo produce un effetto visivamente molto diverso. 63 | 64 | Il Color Smudge Engine di Krita tende a produrre degli edge sfocati, 65 | perché usa un algoritmo di tipo build up e non di tipo glaze. 66 | 67 | 68 | Nota: la pennellata non si sporca col bianco della tela, ma solo con il 69 | colore davvero depositato sulla tela. Questo settaggio è opzionale. 70 | 71 | Questo motore si chiama "auto-mix" perché ogni pennellata si "mixa 72 | automaticamente" col colore di sfondo. 73 | 74 | L'auto-mix funziona con qualunque brush, anche quelli che usano il pixel 75 | engine. Quindi non devi rinunciare al dual brush, che attualmente non è 76 | supportato dal color smudge engine di Krita. E hai gli edge netti. 77 | 78 | 79 | Nota: questa modalità, se attivata, riempie la color history di Krita di 80 | colori intermedi, prodotto dello "sporcamento" del pennello. Questo rende 81 | la color history di Krita inutilizzabile. Per questa ragione, ColorPlus 82 | offre una sua color history, che non mostra questi colori "intermedi". 83 | 84 | Per attivare l'auto mix, cliccare nel docker di ColorPlus: https://i.imgur.com/COOOjwa.png 85 | 86 | Lì c'è anche lo slider che controlla quanto prende dalla tela ad ogni pennellata. 87 | 88 | ## Dirty brush 89 | 90 | In questa modalità, ogni volta che fai una pennellata, il tuo pennello si sporca un po' 91 | con il colore sulla tela. La quantità con cui si sporca è configurabile con uno slider. 92 | 93 | Differenza con l'auto-mix: nel dirty brush, la prima pennellata non si sporca, ma ha 94 | il colore puro che tu hai scelto. Solo le pennellate successive si sporcano in base al 95 | colore che era sulla tela nelle pennellate precedenti. Invece, nell'auto-mix, anche la 96 | prima pennellata si sporca. Ma le successive pennellate dimenticano il modo in cui si 97 | è sporcata la pennellata precedente. 98 | 99 | Un'altra differenza è che, con l'auto-mix, ogni pennellata vi avvicina 100 | sempre di più al colore target (cioè il foreground color). Invece, con 101 | il dirty brush, ogni pennellata vi allontana sempre più dal foreground 102 | color. 103 | 104 | Nota: per pulire il pennello c'è uno shortcut, che è lo stesso per la funzione Dry Paper. 105 | (se il pennello è sporco, oltre a creare un nuovo layer pulisce il pennello) 106 | 107 | Nota: la pennellata non si sporca col bianco della tela, ma solo con il 108 | colore davvero depositato sulla tela. Questo settaggio è opzionale. 109 | 110 | Nota: questa modalità, se attivata, riempie la color history di Krita di 111 | colori intermedi, prodotto dello "sporcamento" del pennello. Questo rende 112 | la color history di Krita inutilizzabile. Per questa ragione, ColorPlus 113 | offre una sua color history, che non mostra questi colori "intermedi". 114 | 115 | Per attivare il dirty Brush, usa il docker di ColorPlus: https://i.imgur.com/RDHdyWS.png 116 | 117 | ## Alternanza rapida tra colori (Previous Color) 118 | 119 | C'è uno shortcut con cui tu puoi alternare facilmente tra due colori, 120 | passando istantaneamente al colore usato prima di quello attuale. 121 | 122 | La maggior parte del tempo, per il mio stile di pittura, io alterno continuamente tra 123 | due colori, quindi questa funzione è essenziale per evitare mouse travel per cliccare 124 | nella cronologia dei colori. 125 | 126 | Con lo stesso shortcut, premuto due volte di seguito, puoi passare 127 | al penultimo colore usato, e così via. 128 | 129 | Consiglio di assegnare questo shortcut al tasto V: https://i.imgur.com/vpKxHaf.png 130 | 131 | (La funzione si chiama Last Color tra gli shortcut di Krita.) 132 | 133 | 134 | ## Color picker intelligente 135 | 136 | Hai uno shortcut per fare color picking del colore sotto al mouse, 137 | senza tenere premuto ALT o CTRL. 138 | 139 | C'è una versione dello shortcut che, oltre a fare color picking, crea anche un nuovo 140 | layer. Consiglio di usare quella. È opportuno creare un nuovo layer ogni volta che si 141 | cambia colore, per simulare l'effetto acquerello (vedi sezione apposita). 142 | 143 | Consiglio di associare questo shortcut al pulsante C: https://i.imgur.com/zC2rmom.png 144 | 145 | ## Color Mixing shortcut 146 | 147 | Hai uno shortcut per mixare il colore attuale (il foreground color) con una porzione 148 | del colore sulla tela. In altre parole, con questo shortcut, cambi il colore attuale 149 | portandolo ad essere più vicino al colore su cui si trova il mouse. 150 | 151 | Ad esempio, se hai il colore blu, e il mouse si trova sul colore giallo, con 152 | questo shortcut puoi aggiungere il 50% di giallo al blu, ottenendo il verde. 153 | 154 | La quantità di colore che prelevi dalla tela è configurabile con lo 155 | slider "mix level" nel docker di ColorPlus. 156 | 157 | Una opzione importante è che, quando mixi un colore per farlo diventare più 158 | simile a un altro, il plugin è in grado di cambiare automaticamente anche la 159 | pennellata che hai appena fatto, dandole il nuovo colore. Questa funzione si 160 | chiama "post-correzione del colore". Vedi sezione apposita. 161 | 162 | Consiglio di associare questa funzione al tasto F: https://i.imgur.com/AiehdQv.png 163 | 164 | ## Post-correzione del colore 165 | 166 | Il plugin, nella sua modalità di default, crea automaticamente un layer ogni volta che 167 | cambi colore. In questo modo, permette la "post-correzione" dei colori sbagliati. Ad 168 | esempio, capita spesso che, dopo aver fatto una pennellata, ti accorgi che il colore è 169 | sbagliato, cioè che armonizza male con lo sfondo, perché è *troppo diverso* dal colore 170 | che c'era sotto, sul quale hai dipinto. In questo caso, puoi modificare il colore "sul 171 | posto", gradualmente, fino a che vedi che è diventato abbastanza simile allo sfondo. 172 | 173 | Come si fa a modificare gradualmente il colore della pennellata appena 174 | fatta? ColorPlus offre 2 modi di farlo: 175 | 176 | 1) Premi il pulsante "color mix" (vedi sezione apposita). Questo, come abbiamo detto, cambierà 177 | il colore del brush portandolo più vicino al colore su cui si trova il mouse. Ma modificherà 178 | anche la pennellata appena fatta (o le pennellate appena fatte) portandole più vicine al colore 179 | sotto al mouse! 180 | 181 | 182 | 2) Diminuisci la trasparenza della pennellata appena fatta. Per questo c'è uno shortcut 183 | apposito. Premi varie volte lo shortcut e vedi le pennellate appena fatte che diventano 184 | più trasparenti. Quando vedi che sono arrivate al punto che non sono più troppo diverse 185 | dal colore sotto, ti fermi, e continui a dipingere con quel livello di trasparenza. 186 | 187 | Questo sistema (sia 1 che 2) è importante perché ti risparmia la sequenza noiosa in cui 188 | fai una pennellata, ti accorgi che il colore è sbagliato, premi Undo, cambi colore dal 189 | selector, provi a pennellare, ti accorgi che non va ancora bene, fai undo, poi cambi di 190 | nuovo colore, e così. via. 191 | 192 | Tutto questo è reso possibile da layer che vengono creati automaticamente. Quindi vedrai 193 | molti layer creati automaticamente. Questo è voluto. Ogni tanto (diciamo ogni 5 minuti) 194 | vorrai probabilmente fonderli tutti insieme, il che si fa con il pulsante cleanup layers 195 | nel docker di ColorPlus: https://i.imgur.com/MTQC8l5.png. 196 | 197 | Puoi disattivare la creazione automatica di layer, ma perderai la 198 | possibilità di fare post-correzioni dei colori. 199 | 200 | Nota: Il fatto che ColorPlus crei automaticamente layer permette anche 201 | l'effetto acquerello (vedi sezione apposita). 202 | 203 | 204 | ### Modificare la trasparenza della pennellata 205 | 206 | Abbiamo detto che c'è uno shortcut per aumentare la trasparenza dell'ultima 207 | pennellata (post-correzione del colore). Consiglio di associarlo al tasto X: 208 | https://i.imgur.com/K8VM3Mx.png 209 | 210 | Di solito, quando aumentate la trasparenza della pennellata perché volete fare 211 | una modifica molto leggera, tipicamente la trasparenza finale sarà molto alta. 212 | Quando poi volete passare a un altro colore, volete anche che la trasparenza 213 | della pennellata sia automaticamente resettata a un valore normale, altrimenti 214 | non vedrete la nuova pennellata. Quindi ColorPlus ha una funzione "auto-reset 215 | opacity": https://i.imgur.com/72jOEa5.png 216 | 217 | Impostate tramite lo slider il livello a cui volete resettare la trasparenza della pennellata, 218 | ogni volta che cambiate colore (consiglio di scegliere un valore tra 70% e 85%). 219 | 220 | 221 | Se vi accorgete che la pennellata cappena fatta ontrasta troppo poco col colore sottostante, 222 | c'è anche uno shortcut per diminuire la trasparenza della pennellata. Consiglio di assegnare 223 | questa funzione al tasto S: https://i.imgur.com/PHRi71L.png 224 | 225 | 226 | 227 | ## Simulazione di acquerello 228 | 229 | L'acquerello è caratterizzato (tra le altre cose!) dal fatto che due pennellate trasparenti si 230 | fondono tra loro. Non vedi la sovrapposizione di colori (overlap). Invece in krita, se usi un 231 | colore trasparente e fai due pennellate, vedrai la sovrapposizione nel punto di intersezione 232 | tra di loro. Le pennellate non si fondono tra loro. 233 | 234 | Per risolvere questo problema, ColorPlus fa sì che sia il layer ad essere trasparente, non 235 | il tuo pennello (che è opaco al 100%). In questo modo il pennello sembrerà trasparente, ma 236 | allo stesso tempo vedrai pennellate che si fondono tra loro senza overlap. ColorPlus si 237 | occupa di creare automaticamente dei layer quando cambi colore, in modo che pennellate con 238 | lo stesso colore si fondano insieme, pur restando trasparenti. 239 | 240 | Nota: Il fatto che ColorPlus crea automaticamente layer permette anche 241 | la post-correzione del colore (vedi sezione apposita). 242 | 243 | Nel momento in cui voleste creare manualmente un layer, basta premere lo shortcut "dry 244 | paper", che consiglio di associare al pulsante D: https://i.imgur.com/A2hIoeT.png 245 | 246 | 247 | 248 | ## Color history intelligente 249 | 250 | Hai una color history visuale, che ti mostra i colori recenti, a cui 251 | puoi switchare cliccandoci direttamente. È simile alla color history 252 | di Krita, ma non ti fa vedere tutti i colori prodotti dall'automix e 253 | dal dirty brush, ma solo quelli originali che tu hai selezionato dal 254 | color selector di Krita. 255 | 256 | 257 | La color history è un docker che potete attivare così: 258 | 259 | Settings -> Dockers -> ColorPlus color history. 260 | 261 | ## Docker principale 262 | 263 | ColorPlus ha un docker principale che racchiude tutti i settaggi che dovete cambiare 264 | di frequente. Per attivarlo: da menu: Settings -> Dockers -> ColorPlus 265 | 266 | Ecco una schermata: https://i.imgur.com/ISNM3wA.png 267 | 268 | Notate anche che nel menu di ColorPlus ci sono alcune funzioni che 269 | mancano nel docker: https://i.imgur.com/bunL1Wt.png 270 | 271 | ## Preview a tutto schermo 272 | 273 | Esiste uno shortcut che mostra il tuo dipinto attuale a tutto schermo, nascondendo 274 | temporaneamente tutti i docker floating che ci sono. Se premuto di nuovo, ripristina 275 | il layout di lavoro, con i docker floating, i docker agganciati, e la reference window 276 | eventuale. 277 | 278 | 279 | Suggerisco di assegnare a questa funzione il pulsante immediatamente sopra al pulsante 280 | TAB, cioè il primo pulsante della tastiera: https://i.imgur.com/BY7BF2O.png 281 | 282 | ## Salvataggio e ripristino delle finestre e della loro posizione 283 | 284 | ColorPlus fornisce un semplice metodo per salvare e ripristinare sessioni di lavoro: salva e 285 | ripristina il layout di lavoro, cioè quali file sono aperti, e in che posizione si trovano. 286 | Trovate due voci di menu per questo: https://i.imgur.com/4ttc0XP.png 287 | 288 | # Esportazione di layer e coordinate 289 | 290 | ColorPlus offre una voce di menu che esporta tutti i layer il cui nome termina con 291 | -png, .png, -jpg o .jpg. Esporta anche un file .json che contiene le coordinate di 292 | ogni layer all'interno dell'immagine. 293 | 294 | Questo è utile se stai sviluppando un gioco o un'applicazione che deve caricare 295 | fondale e personaggi, e sapere in che posizione piazzare i personaggi. 296 | 297 | La funzione esporta anche gruppi di layer. Se hai un gruppo con nome che finisce ad esempio 298 | con .png, esporterà un'immagine contenente la fusione di tutti i layer del gruppo. 299 | 300 | Il .json e i layer vengono salvati nella tua cartella Documenti. 301 | 302 | ## Autofocus delle finestre 303 | 304 | Autofocus vuol dire che la finestra su cui sposti il mouse viene automaticamente attivata. 305 | 306 | Alcune funzioni di ColorPlus hanno bisogno che l'opzione autofocus resti attiva. 307 | Ad esempio, senza di essa non funzionerà la creazione automatica di layer. 308 | 309 | # INSTALLAZIONE 310 | 311 | In Windows: 312 | 313 | Chiudi Krita. Entra nella cartella 314 | 315 | C:\Users\yourname\AppData\Roaming\krita\pykrita 316 | 317 | e copia qui il file recent_color.desktop e la cartella recent_color: 318 | https://i.imgur.com/5SoFMpu.png 319 | 320 | Poi entra nella cartella 321 | 322 | C:\Users\yourname\AppData\Roaming\krita\actions 323 | 324 | e copia qui i file .action: https://i.imgur.com/PR2xWr0.png 325 | 326 | Poi avvia Krita. Da menu: Settings -> configure Krita. Python plugin manager. 327 | 328 | Attiva il plugin spuntandolo qui: https://i.imgur.com/EDr7vdd.png 329 | 330 | Poi riassegna le scorciatoie qui: https://i.imgur.com/7J5ZFXe.png 331 | 332 | (Nella schermata sopra puoi vedere anche quali tasti consiglio di 333 | assegnare alle varie funzioni) 334 | 335 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | 1. [ColorPlus](#colorplus) 4 | 2. [Features](#features) 5 | * [Spectral color mixing logic](#spectral-color-mixing-logic) 6 | * [Auto-mix](#auto-mix) 7 | * [Dirty brush](#dirty-brush) 8 | * [Quick color switching (Previous Color)](#quick-color-switching-previous-color) 9 | * [Smart color picker](#smart-color-picker) 10 | * [Color Mixing shortcut](#color-mixing-shortcut) 11 | * [Color post-correction](#color-post-correction) 12 | * [Modify stroke transparency](#modify-stroke-transparency) 13 | * [Watercolor simulation](#watercolor-simulation) 14 | * [Smart color history](#smart-color-history) 15 | * [Main docker](#main-docker) 16 | * [Full-screen preview](#full-screen-preview) 17 | * [Saving and restoring windows and their positions](#saving-and-restoring-windows-and-their-positions) 18 | * [Exporting layers and coordinates](#exporting-layers-and-coordinates) 19 | * [Window autofocus](#window-autofocus) 20 | 3. [INSTALLATION](#installation) 21 | * [On Windows](#on-windows) 22 | 23 | # ColorPlus 24 | 25 | ColorPlus is a plugin for Krita that adds the following features to Krita: 26 | 27 | # Features 28 | 29 | ## Spectral color mixing logic 30 | 31 | This means that colors mix realistically. For example, when you mix two colors, yellow mixed 32 | with blue produces green; blue with white produces a bright and vibrant blue; etc. 33 | 34 | This realistic mixing occurs in all ColorPlus features: manual mixing, auto-mix, dirty brush. 35 | 36 | Thanks to Ronald van Wijnen for the library used for color mixing 37 | according to the Kubelka-Munk theory! 38 | 39 | ## Auto-mix 40 | 41 | If you activate this mode, every brush stroke you make automatically 42 | gets dirty with the color that is already present on the canvas. The 43 | proportion in which it gets dirty is configurable with a slider. 44 | 45 | 46 | Each stroke gets dirty independently. That is, with each stroke, the brush is 47 | first "auto-cleaned", then dirtied with the new color on the canvas. (If you 48 | want the brush to remain dirty between strokes, you can use the dirty brush. 49 | See the dedicated section). 50 | 51 | So, with each stroke, you will introduce a bit of color. By continuing to brush, it 52 | will eventually reach the target color. (Provided the mixing radius is low enough, 53 | otherwise you will never reach it). 54 | 55 | Differences with Krita's Color Smudge Engine: 56 | 57 | Krita's Color Smudge mixes colors without spectral logic, thus producing 58 | faded colors: for example, blue + yellow = gray, not green. 59 | 60 | Krita's Color Smudge Engine works with the concept of "color rate", meaning your chosen color 61 | (foreground color) is gradually introduced during the stroke. You cannot have a stroke that 62 | from the beginning has your foreground color, but dirtied by a fixed percentage with the color 63 | on the canvas. This produces a visually very different effect. 64 | 65 | Krita's Color Smudge Engine tends to produce blurry edges, because 66 | it uses a build-up type algorithm and not a glaze type. 67 | 68 | 69 | Note: the stroke does not get dirty with the white of the canvas, but only 70 | with the color actually deposited on the canvas. This setting is optional. 71 | 72 | This engine is called "auto-mix" because each stroke "automatically 73 | mixes" with the background color. 74 | 75 | Auto-mix works with any brush, even those using the pixel engine. So you don't have 76 | to give up the dual brush, which is currently not supported by Krita's color smudge 77 | engine. And you get sharp edges. 78 | 79 | 80 | Note: this mode, if activated, fills Krita's color history with intermediate colors, produced 81 | by the "dirtying" of the brush. This makes Krita's color history unusable. For this reason, 82 | ColorPlus offers its own color history, which does not show these "intermediate" colors. 83 | 84 | To activate auto-mix, click in the ColorPlus docker: https://i.imgur.com/COOOjwa.png 85 | 86 | There is also the slider that controls how much it takes from the canvas with each stroke. 87 | 88 | ## Dirty brush 89 | 90 | In this mode, every time you make a stroke, your brush gets a little dirty with the 91 | color on the canvas. The amount it gets dirty is configurable with a slider. 92 | 93 | Difference with auto-mix: in dirty brush, the first stroke does not get dirty, but has the 94 | pure color you chose. Only subsequent strokes get dirty based on the color that was on the 95 | canvas in previous strokes. Instead, in auto-mix, even the first stroke gets dirty. But 96 | subsequent strokes forget how the previous stroke got dirty. 97 | 98 | Another difference is that, with auto-mix, each stroke brings you closer and closer to the 99 | target color (i.e., the foreground color). Instead, with the dirty brush, each stroke takes 100 | you further and further away from the foreground color. 101 | 102 | Note: There is a shortcut to clean the brush, which is the same one used for the Dry Paper 103 | function. (If the brush is dirty, besides creating a new layer, it also cleans the brush). 104 | 105 | Note: the stroke does not get dirty with the white of the canvas, but only 106 | with the color actually deposited on the canvas. This setting is optional. 107 | 108 | Note: this mode, if activated, fills Krita's color history with intermediate colors, produced 109 | by the "dirtying" of the brush. This makes Krita's color history unusable. For this reason, 110 | ColorPlus offers its own color history, which does not show these "intermediate" colors. 111 | 112 | To activate the Dirty Brush, use the ColorPlus docker: https://i.imgur.com/RDHdyWS.png 113 | 114 | ## Quick color switching (Previous Color) 115 | 116 | There is a shortcut with which you can easily switch between two colors, 117 | instantly switching to the color used before the current one. 118 | 119 | Most of the time, for my painting style, I continuously switch between two colors, 120 | so this function is essential to avoid mouse travel to click in the color history. 121 | 122 | With the same shortcut, pressed twice in a row, you can switch to 123 | the second to last color used, and so on. 124 | 125 | I recommend assigning this shortcut to the V key: https://i.imgur.com/vpKxHaf.png 126 | 127 | 128 | ## Smart color picker 129 | 130 | You have a shortcut to color pick the color under the mouse, without holding down ALT or CTRL. 131 | 132 | There is a version of the shortcut that, in addition to color picking, also creates 133 | a new layer. I recommend using that one. It is advisable to create a new layer every 134 | time you change color, to simulate the watercolor effect (see dedicated section). 135 | 136 | I recommend associating this shortcut with the C button: https://i.imgur.com/zC2rmom.png 137 | 138 | ## Color Mixing shortcut 139 | 140 | You have a shortcut to mix the current color (the foreground color) with a 141 | portion of the color on the canvas. In other words, with this shortcut, you 142 | change the current color bringing it closer to the color the mouse is over. 143 | 144 | For example, if you have the color blue, and the mouse is over the color yellow, 145 | with this shortcut you can add 50% yellow to blue, obtaining green. 146 | 147 | The amount of color you pick from the canvas is configurable with 148 | the "mix level" slider in the ColorPlus docker. 149 | 150 | An important option is that, when you mix a color to make it more similar to another, 151 | the plugin is able to automatically change also the stroke you just made, giving it the 152 | new color. This function is called "color post-correction". See the dedicated section. 153 | 154 | I recommend associating this function with the F key: https://i.imgur.com/AiehdQv.png 155 | 156 | ## Color Post-correction 157 | 158 | The plugin, in its default mode, automatically creates a layer every time you change color. 159 | This way, it allows "post-correction" of wrong colors. For example, it often happens that, 160 | after making a stroke, you realize the color is wrong, meaning it harmonizes poorly with 161 | the background, because it is *too different* from the color that was underneath, on which 162 | you painted. In this case, you can modify the color "on the spot", gradually, until you see 163 | it has become similar enough to the background. 164 | 165 | How do you gradually modify the color of the stroke just made? ColorPlus 166 | offers 2 ways to do it: 167 | 168 | 1) Press the "color mix" button (see dedicated section). This, as we said, will 169 | change the brush color bringing it closer to the color the mouse is over. But it 170 | will also modify the stroke just made (or the strokes just made) bringing them 171 | closer to the color under the mouse! 172 | 173 | 174 | 2) Decrease the transparency of the stroke just made. There is a dedicated shortcut 175 | for this. Press the shortcut several times and see the strokes just made become more 176 | transparent. When you see they have reached the point where they are no longer too 177 | different from the color underneath, you stop, and continue painting with that level 178 | of transparency. 179 | 180 | This system (both 1 and 2) is important because it saves you the tedious sequence where you 181 | make a stroke, realize the color is wrong, press Undo, change color from the selector, try 182 | to brush, realize it's still not right, do undo, then change color again, and so on. 183 | 184 | All this is made possible by layers that are created automatically. So you will see 185 | many layers created automatically. This is intentional. Every so often (say every 5 186 | minutes) you will probably want to merge them all together, which is done with the 187 | cleanup layers button in the ColorPlus docker: https://i.imgur.com/MTQC8l5.png. 188 | 189 | You can disable automatic layer creation, but you will lose the ability 190 | to make post-corrections of colors. 191 | 192 | Note: The fact that ColorPlus automatically creates layers also allows 193 | the watercolor effect (see dedicated section). 194 | 195 | 196 | ### Modify stroke transparency 197 | 198 | We said there is a shortcut to increase the transparency of the last 199 | stroke (color post-correction). I recommend associating it with the X 200 | key: https://i.imgur.com/K8VM3Mx.png 201 | 202 | Usually, when you increase the transparency of the stroke because you want to make a 203 | very slight modification, typically the final transparency will be very high. When you 204 | then want to switch to another color, you also want the transparency of the stroke to 205 | be automatically reset to a normal value, otherwise you won't see the new stroke. So 206 | ColorPlus has an "auto-reset opacity" function: https://i.imgur.com/72jOEa5.png 207 | 208 | Set via the slider the level to which you want to reset the stroke transparency, 209 | every time you change color (I recommend choosing a value between 70% and 85%). 210 | 211 | 212 | If you notice that the stroke just made contrasts too little with the underlying 213 | color, there is also a shortcut to decrease the transparency of the stroke. I 214 | recommend assigning this function to the S key: https://i.imgur.com/PHRi71L.png 215 | 216 | 217 | 218 | ## Watercolor simulation 219 | 220 | Watercolor is characterized (among other things!) by the fact that two transparent 221 | strokes blend together. You don't see the overlap of colors. Instead in Krita, if 222 | you use a transparent color and make two strokes, you will see the overlap at the 223 | intersection point between them. The strokes do not blend together. 224 | 225 | To solve this problem, ColorPlus makes the layer transparent, not your 226 | brush (which is 100% opaque). This way the brush will seem transparent, 227 | but at the same time you will see strokes that blend together without 228 | overlap. ColorPlus takes care of automatically creating layers when you 229 | change color, so that strokes with the same color blend together, while 230 | remaining transparent. 231 | 232 | Note: The fact that ColorPlus automatically creates layers also allows 233 | color post-correction (see dedicated section). 234 | 235 | When you want to manually create a layer, just press the "dry paper" shortcut, which 236 | I recommend associating with the D button: https://i.imgur.com/A2hIoeT.png 237 | 238 | 239 | 240 | ## Smart color history 241 | 242 | You have a visual color history, which shows you recent colors, to which you can 243 | switch by clicking directly on them. It is similar to Krita's color history, but 244 | it doesn't show you all the colors produced by automix and dirty brush, but only 245 | the original ones that you selected from Krita's color selector. 246 | 247 | 248 | The color history is a docker that you can activate like this: 249 | 250 | Settings -> Dockers -> ColorPlus color history. 251 | 252 | ## Main docker 253 | 254 | ColorPlus has a main docker that contains all the settings you need to change 255 | frequently. To activate it: from the menu: Settings -> Dockers -> ColorPlus 256 | 257 | Here is a screenshot: https://i.imgur.com/ISNM3wA.png 258 | 259 | Note also that in the ColorPlus menu there are some functions that 260 | are missing in the docker: https://i.imgur.com/bunL1Wt.png 261 | 262 | ## Full-screen preview 263 | 264 | There is a shortcut that shows your current painting in full screen, temporarily hiding all 265 | floating dockers. If pressed again, it restores the working layout, with floating dockers, 266 | docked dockers, and the reference window if any. 267 | 268 | 269 | I suggest assigning this function to the button immediately above the TAB button, 270 | i.e., the first button on the keyboard: https://i.imgur.com/BY7BF2O.png 271 | 272 | ## Saving and restoring windows and their positions 273 | 274 | ColorPlus provides a simple method for saving and restoring work sessions: it saves 275 | and restores the work layout, i.e., which files are open, and in what position they 276 | are located. You find two menu items for this: https://i.imgur.com/4ttc0XP.png 277 | 278 | # Exporting layers and coordinates 279 | 280 | ColorPlus offers a menu item that exports all layers whose name ends with -png, .png, -jpg or 281 | .jpg. It also exports a .json file containing the coordinates of each layer within the image. 282 | 283 | This is useful if you are developing a game or application that needs to 284 | load backgrounds and characters, and know where to place the characters. 285 | 286 | The function also exports layer groups. If you have a group with a name ending for example 287 | with .png, it will export an image containing the merge of all layers in the group. 288 | 289 | The .json and layers are saved in your Documents folder. 290 | 291 | ## Window autofocus 292 | 293 | Autofocus means that the window you move the mouse over is automatically activated. 294 | 295 | Some ColorPlus functions need the autofocus option to remain active. 296 | For example, without it, automatic layer creation will not work. 297 | 298 | # INSTALLATION 299 | 300 | On Windows: 301 | 302 | Close Krita. Go to the folder 303 | 304 | C:\Users\yourname\AppData\Roaming\krita\pykrita 305 | 306 | and copy the recent_color.desktop file and the recent_color folder here: 307 | https://i.imgur.com/5SoFMpu.png 308 | 309 | Then go to the folder 310 | 311 | C:\Users\yourname\AppData\Roaming\krita\actions 312 | 313 | and copy the .action files here: https://i.imgur.com/PR2xWr0.png 314 | 315 | (before copying them, it would be better to delete the .action files that are already 316 | there, but be sure not to delete action files that are from other plugins!) 317 | 318 | Then start Krita. From the menu: Settings -> configure Krita. Python plugin manager. 319 | 320 | Activate the plugin by checking it here: https://i.imgur.com/EDr7vdd.png 321 | 322 | Then reassign the shortcuts here: https://i.imgur.com/7J5ZFXe.png 323 | 324 | (In the screenshot above you can also see which keys I recommend 325 | assigning to the various functions) -------------------------------------------------------------------------------- /actions/cleanupLayers.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cleanup wet layers 5 | 6 | 7 | 8 | Cleanup layer list by merging all wet layers 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/cycle_next_brush.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cycle to Next Brush 5 | 6 | 7 | 8 | Cycle to Next Brush 9 | Manually cycles to the next brush in the configured list if auto-cycling is off. 10 | Cycle to Next Brush (Manual) 11 | 12 | 0 13 | 0 14 | 15 | false 16 | Manually cycle to the next brush in the list 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/decrease_layer_opacity.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Decrease current layer opacity 5 | 6 | 7 | 8 | Decrease opacity of current layer 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/dry_paper.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dry paper 5 | 6 | 7 | 8 | Dry the paper (creates a new layer and sets its opacity) 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/dry_paper_and_pick_color.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dry paper and pick color 5 | 6 | 7 | 8 | Dry the paper and pick color under cursor with a single key press 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/increase_layer_opacity.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Increase current layer opacity 5 | 6 | 7 | 8 | Increase opacity of current layer 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/last_color.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Last Color 5 | 6 | 7 | 8 | Switch to last used color 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/mix_color_because_mistake.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mix color with post-correction 5 | 6 | 7 | 8 | Makes the current color more similar to the color under the mouse, and also modifies the already made brushstroke to give it that color. 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/mix_color_because_want_to_fadeout.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mix color 5 | 6 | 7 | 8 | Makes the current color more similar to the color under the mouse. Does not alter the already made brushstroke. 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/pick_color.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pick Color 5 | 6 | 7 | 8 | Pick color from current layer with a simple keypress 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/reset_layer_opacity_to_default.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reset layer opacity to default 5 | 6 | 7 | 8 | Manually reset the current layer opacity to default 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/toggle_auto_mixing.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Toggle auto-mixing 5 | 6 | 7 | 8 | Enable or disable auto-mixing 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/toggle_dirty_brush.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Toggle dirty brush 5 | 6 | 7 | 8 | Enable or disable the dirty brush 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /actions/view_single_window.action: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Preview single window 5 | 6 | 7 | 8 | Minimize all always-on-top windows, then view full screen. Press again to restore previous state. 9 | 10 | 11 | 12 | 0 13 | 0 14 | 15 | false 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /install_gui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import sys 4 | import tkinter as tk 5 | from tkinter import messagebox, font, scrolledtext 6 | 7 | def get_files_to_install(): 8 | """Gets a list of (source_path, destination_path) tuples for all files.""" 9 | files_to_install = [] 10 | try: 11 | script_dir = os.path.dirname(os.path.abspath(__file__)) 12 | appdata_dir = os.getenv('APPDATA') 13 | if not appdata_dir: 14 | raise EnvironmentError("Could not find APPDATA environment variable.") 15 | 16 | # 1. Python Plugin Directory 17 | py_source_dir_rel = os.path.join('pykrita', 'recent_color') 18 | py_source_dir_abs = os.path.join(script_dir, py_source_dir_rel) 19 | py_dest_dir = os.path.join(appdata_dir, 'krita', 'pykrita', 'recent_color') # Destination is the directory itself 20 | if os.path.isdir(py_source_dir_abs): # Check if source directory exists 21 | # Mark this as a directory copy operation 22 | files_to_install.append(('dir', py_source_dir_abs, py_dest_dir)) 23 | else: 24 | print(f"Warning: Python source directory not found: {py_source_dir_abs}") 25 | 26 | 27 | # 2. Action Files 28 | actions_source_dir = os.path.join(script_dir, 'actions') 29 | actions_dest_dir = os.path.join(appdata_dir, 'krita', 'actions') 30 | 31 | if os.path.isdir(actions_source_dir): 32 | for filename in os.listdir(actions_source_dir): 33 | if filename.lower().endswith('.action'): 34 | action_source_abs = os.path.join(actions_source_dir, filename) 35 | action_dest_abs = os.path.join(actions_dest_dir, filename) 36 | # Mark this as a file copy operation 37 | files_to_install.append(('file', action_source_abs, action_dest_abs)) 38 | else: 39 | print(f"Warning: Actions source directory not found: {actions_source_dir}") 40 | 41 | 42 | if not files_to_install: 43 | raise FileNotFoundError("No plugin or action files found to install. Ensure script is in project root.") 44 | 45 | return files_to_install 46 | 47 | except Exception as e: 48 | messagebox.showerror("Error Getting Paths", f"Could not determine file paths:\n{e}") 49 | sys.exit(1) 50 | 51 | def install_plugin(): 52 | """Handles the installation process for all files.""" 53 | try: 54 | files_to_copy = get_files_to_install() 55 | except SystemExit: 56 | return # Error already shown by get_files_to_install 57 | 58 | # Confirmation message box removed as requested. 59 | # The preview in the text area serves as confirmation. 60 | 61 | dest_dirs = set() 62 | for op_type, src, dest in files_to_copy: 63 | if op_type == 'file': 64 | dest_dirs.add(os.path.dirname(dest)) # Collect unique destination directories for files 65 | # For 'dir' type, copytree handles directory creation, but we still need the parent dir for actions 66 | elif op_type == 'dir': 67 | # Ensure the parent of the destination directory exists if needed (e.g., %APPDATA%/krita/pykrita might not exist) 68 | parent_dest_dir = os.path.dirname(dest) 69 | if parent_dest_dir: # Avoid adding empty string if dest is top-level (unlikely here) 70 | dest_dirs.add(parent_dest_dir) 71 | 72 | try: 73 | # Create all destination directories first 74 | for d_dir in dest_dirs: 75 | print(f"Ensuring directory exists: {d_dir}") 76 | os.makedirs(d_dir, exist_ok=True) 77 | 78 | # Copy all files 79 | copied_files = [] 80 | errors = [] 81 | for op_type, src, dest in files_to_copy: 82 | try: 83 | if op_type == 'dir': 84 | print(f"Copying directory {src} to {dest}") 85 | # Remove existing destination directory first to avoid merge issues or errors if it's not empty 86 | if os.path.exists(dest): 87 | print(f"Removing existing destination directory: {dest}") 88 | shutil.rmtree(dest) 89 | shutil.copytree(src, dest) # Using copytree for directories 90 | copied_files.append(dest + " (directory)") # Mark as directory 91 | elif op_type == 'file': 92 | print(f"Copying file {src} to {dest}") 93 | shutil.copy2(src, dest) # copy2 preserves metadata 94 | copied_files.append(dest) 95 | except Exception as copy_e: 96 | item_name = os.path.basename(src) 97 | item_type = "directory" if op_type == 'dir' else "file" 98 | errors.append(f"Failed to copy {item_type} {item_name}: {copy_e}") 99 | 100 | if errors: 101 | messagebox.showwarning("Installation Issues", "Some files failed to copy:\n\n" + "\n".join(errors)) 102 | elif copied_files: 103 | messagebox.showinfo("Success", f"Plugin files installed successfully.\n({len(copied_files)} files copied)\n\nPlease restart Krita.") 104 | # root.destroy() # Removed as GUI is no longer used 105 | else: 106 | messagebox.showinfo("No Files Copied", "No files were copied (perhaps source files are missing?).") 107 | 108 | 109 | except Exception as e: 110 | messagebox.showerror("Installation Failed", f"An error occurred during installation:\n{e}\n\nPlease check permissions or if Krita is running.") 111 | 112 | # --- Direct Installation --- 113 | # GUI removed, call install function directly. 114 | if __name__ == "__main__": 115 | # Hide the dummy Tkinter root window that messagebox creates 116 | root = tk.Tk() 117 | root.withdraw() 118 | install_plugin() -------------------------------------------------------------------------------- /pykrita/recent_color.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Service 3 | ServiceTypes=Krita/PythonPlugin 4 | X-KDE-Library=recent_color 5 | X-Python-2-Compatible=false 6 | X-Krita-Manual=myPluginManual.html 7 | Name=ColorPlus 8 | Comment=This plugin enhances Krita with spectral color mixing logic, auto-mix and dirty brush modes, quick color switching, smart color picking and mixing, post-stroke color correction, watercolor simulation, a selective color history, fullscreen preview, and layer/coordinate exporting. -------------------------------------------------------------------------------- /pykrita/recent_color/TODO.md: -------------------------------------------------------------------------------- 1 | DONE quando cambio colore, prendi sempre il primo brush del ciclo. 2 | che probabilmente è quello con texture. 3 | 4 | DONE alternare brush automaticamente: può avere senso per brush di tipo setole, molto 5 | simili, per dare varietà. ma se cambio il size di uno, come faccio a cambiare il size 6 | degli altri? ma forse quando switch posso copiare il size del nuovo nel vecchio. 7 | 8 | DONE fai che clean brush biene fatto da dry paper, non da V 9 | 10 | DONE automix prevale su dirty brush 11 | 12 | TODO manca krita palette 13 | 14 | difficile perché cambia con un delay il color selector. ho provato a leggere il colore 15 | con delay, ma se in quel momento ho gia iniziato lo stroke, krita crasha. 16 | 17 | DONE c'è ancora il bug nell automix: scegli un colore nel selector, e quando 18 | esci ti setta un altro, precedente. non succede dall'inizoi ma dopo un po' 19 | 20 | riscrivi tutto su questa idea: se arriva il fgcolor changed MENTRE TU SEI FUORI, allora e' un colore vero. 21 | 22 | 23 | DONE C non crea il nuovo layer? o solo se non sei in un group? 24 | 25 | DONE vedi se pulsante V crea nuovo layer 26 | 27 | 28 | 29 | DONE manca gestire il click nella history 30 | 31 | click su color history inizia a funzionare ma crea duplicati 32 | 33 | DONE non crea il nuovo layer 34 | 35 | 36 | 37 | DONE manca gestire C 38 | 39 | DONE gestire anche grayscale quando fai C. l'ho commentato 40 | 41 | DONE gestire mix shortcut 42 | 43 | 44 | DONE fixa il bug di quando fa fill dell'intero layer. come si riproduceva? prima dry e poi F 45 | 46 | DONE inverti dry paper e color preview 47 | 48 | DONE vedi ultima versione di spectral 49 | 50 | TODO quando togli dirty brush, deve fare accept current color. forse 51 | solo metterlo nella history. 52 | 53 | TODO on hold: col polling posso togliere autofocus windows! che crea un sacco di problemi? 54 | ma no, non posso. non posso creare un layer senza rendere attiva la subwindow. 55 | 56 | 57 | DONE ritesta save e restore state and pos of all windows 58 | 59 | DONE reimplementa export layer coordinates 60 | 61 | DONE pulsante merge temp layers 62 | 63 | DONE disabilita sliders visualmente 64 | 65 | DONE con automix, se premo V, non mette il colore in cima alla history 66 | 67 | DONE cercando altro bug: non crea piu il layer nemmeno senza automix 68 | 69 | DONE automix: se premo V per andare al colore precedente, dopo il 70 | primo stroke appena lascio mi rimette l'altro 71 | 72 | DONE ritesta layer opacity 73 | 74 | DONE ritesta full screen preview 75 | 76 | ok ma se era massimizzata, rimassimizzala quando ripremi 77 | 78 | DONE mi sembra che il mixing spctral non funzioni piu bene da quando ho fatto il radius 79 | 80 | DONE se premo D, fare clean brush se sporco 81 | 82 | DONE mi crea un nuovo layer se esco e rientro con mouseover dal selector, 83 | anche se non ha cambiato colore 84 | 85 | TODO low priority - ad ogni mouse up viene rebuildata la color history. 86 | per verificarlo, scommenta qualcosa dentro update_color_history_ui 87 | 88 | TODO è inutile il brush cycling così, ma con shortcut può avere senso 89 | 90 | per ora commentato -------------------------------------------------------------------------------- /pykrita/recent_color/__init__.py: -------------------------------------------------------------------------------- 1 | from .recent_color import MyExtension -------------------------------------------------------------------------------- /pykrita/recent_color/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seguso/KritaColorPlus/af260745e0924010c2ad08c2af1c4ebeb774e55d/pykrita/recent_color/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /pykrita/recent_color/__pycache__/recent_color.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seguso/KritaColorPlus/af260745e0924010c2ad08c2af1c4ebeb774e55d/pykrita/recent_color/__pycache__/recent_color.cpython-38.pyc -------------------------------------------------------------------------------- /pykrita/recent_color/brush_cycler.py: -------------------------------------------------------------------------------- 1 | # brush_cycler.py 2 | # Module to handle automatic brush cycling after each stroke 3 | 4 | from krita import Krita # type: ignore 5 | 6 | from . import globals as g 7 | 8 | from .recent_color import log 9 | 10 | class BrushCycler: 11 | """Class to manage cycling through a list of brushes automatically""" 12 | 13 | def __init__(self): 14 | self.enabled = False 15 | self.brush_list = [] # List of brush preset names to cycle through 16 | g.g_brushCyclerIndex = 0 17 | self.load_settings() 18 | 19 | def load_settings(self): 20 | """Load brush cycler settings from Krita configuration""" 21 | app = Krita.instance() 22 | 23 | # non voglio che all'avvio parta il brush cycler 24 | #self.enabled = app.readSetting("colorPlus", "brush_cycler_enabled", "false").lower() == "true" 25 | 26 | # Load saved brush list 27 | brush_list_str = app.readSetting("colorPlus", "brush_cycler_list", "") 28 | if brush_list_str: 29 | self.brush_list = brush_list_str.split(",") 30 | else: 31 | self.brush_list = [] 32 | 33 | 34 | 35 | 36 | def save_settings(self): 37 | """Save brush cycler settings to Krita configuration""" 38 | app = Krita.instance() 39 | #app.writeSetting("colorPlus", "brush_cycler_enabled", str(self.enabled).lower()) 40 | app.writeSetting("colorPlus", "brush_cycler_list", ",".join(self.brush_list)) 41 | 42 | 43 | def toggle_enabled(self): 44 | """Toggle brush cycling on/off""" 45 | self.enabled = not self.enabled 46 | self.save_settings() 47 | return self.enabled 48 | 49 | def set_brush_list(self, brush_list): 50 | """Set the list of brushes to cycle through""" 51 | if not isinstance(brush_list, list): 52 | raise TypeError("brush_list must be a list of brush preset names") 53 | 54 | # Verify all brushes exist 55 | all_presets = Krita.instance().resources('preset') 56 | valid_brushes = [] 57 | 58 | for brush_name in brush_list: 59 | # Check if the brush exists in available presets 60 | found = False 61 | for preset_key, preset in all_presets.items(): 62 | if preset.name() == brush_name: 63 | valid_brushes.append(brush_name) 64 | found = True 65 | break 66 | 67 | if not found: 68 | log(f"Warning: Brush preset '{brush_name}' not found and will be skipped") 69 | 70 | self.brush_list = valid_brushes 71 | g.g_brushCyclerIndex = 0 if valid_brushes else 0 72 | self.save_settings() 73 | 74 | def add_current_brush(self): 75 | """Add the currently active brush to the cycle list""" 76 | app = Krita.instance() 77 | view = app.activeWindow().activeView() 78 | 79 | if view: 80 | # Get current brush preset 81 | current_preset = view.currentBrushPreset() 82 | if current_preset: 83 | brush_name = current_preset.name() 84 | 85 | # Add to list if not already present 86 | if brush_name not in self.brush_list: 87 | self.brush_list.append(brush_name) 88 | self.save_settings() 89 | return True 90 | 91 | return False 92 | 93 | def remove_brush(self, brush_name): 94 | """Remove a brush from the cycle list""" 95 | if brush_name in self.brush_list: 96 | self.brush_list.remove(brush_name) 97 | # Adjust current index if needed 98 | if g.g_brushCyclerIndex >= len(self.brush_list): 99 | g.g_brushCyclerIndex = 0 if self.brush_list else 0 100 | self.save_settings() 101 | return True 102 | return False 103 | 104 | def clear_brush_list(self): 105 | """Clear the brush cycle list""" 106 | self.brush_list = [] 107 | g.g_brushCyclerIndex = 0 108 | self.save_settings() 109 | 110 | def cycle_to_next_brush(self): 111 | """Cycle to the next brush in the list and apply it, preserving size""" 112 | if not self.brush_list: 113 | return False 114 | 115 | 116 | log("cycle to next brush") 117 | 118 | app = Krita.instance() 119 | window = app.activeWindow() 120 | if not window: 121 | return False 122 | view = window.activeView() 123 | if not view: 124 | return False 125 | 126 | # Get current brush size 127 | previous_size = view.brushSize() 128 | 129 | # Move to next brush 130 | g.g_brushCyclerIndex = (g.g_brushCyclerIndex + 1) % len(self.brush_list) 131 | 132 | # Apply the brush 133 | applied_successfully = self.apply_current_brush() 134 | 135 | # Restore the previous size if the brush was applied 136 | if applied_successfully: 137 | # Re-get view just in case, though likely unnecessary 138 | view = Krita.instance().activeWindow().activeView() 139 | if view: 140 | view.setBrushSize(previous_size) 141 | log(f"Restored brush size to: {previous_size}") 142 | 143 | return applied_successfully 144 | 145 | def apply_current_brush(self): 146 | """Apply the current brush in the cycle""" 147 | if not self.brush_list: 148 | return False 149 | 150 | app = Krita.instance() 151 | window = app.activeWindow() 152 | if not window: 153 | return False 154 | 155 | view = window.activeView() 156 | if not view: 157 | return False 158 | 159 | # Get the brush name 160 | brush_name = self.brush_list[g.g_brushCyclerIndex] 161 | 162 | # Find the brush preset 163 | log(f"Attempting to apply brush: '{brush_name}' (Index: {g.g_brushCyclerIndex})") # DEBUG 164 | all_presets = app.resources('preset') 165 | 166 | # self.debug_print_all_brushes() 167 | 168 | for preset_key, preset in all_presets.items(): 169 | if preset.name() == brush_name: 170 | # Apply the brush preset using setCurrentBrushPreset 171 | view.setCurrentBrushPreset(preset) 172 | log(f"Switched to brush: {brush_name}") 173 | self.save_settings() 174 | return True 175 | 176 | log(f"Error: Could not find brush preset '{brush_name}'") 177 | # DEBUG: Print available names if not found 178 | available_names = [p.name() for p_key, p in all_presets.items()] 179 | available_names.sort() 180 | log(f"Available presets ({len(available_names)}): {', '.join(available_names[:20])}...") # Print first 20 181 | return False 182 | 183 | def debug_print_all_brushes(self): 184 | """Debug function to print all available brush presets in Krita""" 185 | app = Krita.instance() 186 | all_presets = app.resources('preset') 187 | 188 | log("=== DEBUG: All available brush presets ===") 189 | log(f"Total number of presets: {len(all_presets)}") 190 | 191 | # Sort brush names alphabetically for easier reading 192 | brush_names = [] 193 | for preset_key, preset in all_presets.items(): 194 | brush_names.append(preset.name()) 195 | log(f"Preset key: {preset_key}") 196 | 197 | brush_names.sort() 198 | for i, name in enumerate(brush_names): 199 | log(f"{i+1}. {name}") 200 | 201 | log("=== End of brush presets list ===") 202 | return brush_names 203 | 204 | # Create a global instance 205 | brush_cycler = BrushCycler() -------------------------------------------------------------------------------- /pykrita/recent_color/brush_list_widget.py: -------------------------------------------------------------------------------- 1 | # brush_list_widget.py 2 | # Widget to display and edit the list of brushes in the brush cycler 3 | 4 | from PyQt5.QtWidgets import ( 5 | QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QListWidgetItem, 6 | QLabel, QDialog, QDialogButtonBox, QAbstractItemView, QMessageBox 7 | ) 8 | from PyQt5.QtCore import Qt, pyqtSignal 9 | from PyQt5.QtGui import QIcon 10 | from krita import Krita 11 | from . import globals as g 12 | from .brush_cycler import brush_cycler 13 | from .recent_color import log 14 | 15 | class BrushListWidget(QWidget): 16 | """Widget to display and edit the list of brushes in the brush cycler""" 17 | 18 | brushListChanged = pyqtSignal() # Signal emitted when the brush list changes 19 | 20 | def __init__(self, parent=None): 21 | super().__init__(parent) 22 | self.setup_ui() 23 | self.update_list() 24 | 25 | def setup_ui(self): 26 | """Set up the user interface""" 27 | # Main layout 28 | main_layout = QVBoxLayout(self) 29 | main_layout.setContentsMargins(0, 0, 0, 0) 30 | 31 | # Title label 32 | title_label = QLabel("Brush Cycle List") 33 | title_label.setAlignment(Qt.AlignCenter) 34 | main_layout.addWidget(title_label) 35 | 36 | # List widget to display brushes 37 | self.brush_list = QListWidget() 38 | self.brush_list.setDragDropMode(QAbstractItemView.InternalMove) # Allow reordering by drag and drop 39 | self.brush_list.setSelectionMode(QAbstractItemView.SingleSelection) 40 | self.brush_list.model().rowsMoved.connect(self.on_rows_moved) # Connect to rowsMoved signal 41 | main_layout.addWidget(self.brush_list) 42 | 43 | # Buttons layout 44 | buttons_layout = QHBoxLayout() 45 | 46 | # Add current brush button 47 | self.add_button = QPushButton("+") 48 | self.add_button.setToolTip("Add current brush to list") 49 | self.add_button.clicked.connect(self.add_current_brush) 50 | buttons_layout.addWidget(self.add_button) 51 | 52 | # Remove selected brush button 53 | self.remove_button = QPushButton("-") 54 | self.remove_button.setToolTip("Remove selected brush from list") 55 | self.remove_button.clicked.connect(self.remove_selected_brush) 56 | buttons_layout.addWidget(self.remove_button) 57 | 58 | # Move up button 59 | self.up_button = QPushButton("↑") 60 | self.up_button.setToolTip("Move selected brush up") 61 | self.up_button.clicked.connect(self.move_brush_up) 62 | buttons_layout.addWidget(self.up_button) 63 | 64 | # Move down button 65 | self.down_button = QPushButton("↓") 66 | self.down_button.setToolTip("Move selected brush down") 67 | self.down_button.clicked.connect(self.move_brush_down) 68 | buttons_layout.addWidget(self.down_button) 69 | 70 | # Clear list button 71 | self.clear_button = QPushButton("Clear") 72 | self.clear_button.setToolTip("Clear brush list") 73 | self.clear_button.clicked.connect(self.clear_brush_list) 74 | buttons_layout.addWidget(self.clear_button) 75 | 76 | main_layout.addLayout(buttons_layout) 77 | 78 | def update_list(self): 79 | """Update the list widget with the current brush list""" 80 | self.brush_list.clear() 81 | 82 | for i, brush_name in enumerate(brush_cycler.brush_list): 83 | item = QListWidgetItem(brush_name) 84 | # Highlight the current brush 85 | if i == g.g_brushCyclerIndex: 86 | font = item.font() 87 | font.setBold(True) 88 | item.setFont(font) 89 | item.setText(f"{brush_name} (current)") 90 | 91 | self.brush_list.addItem(item) 92 | 93 | def add_current_brush(self): 94 | """Add the current brush to the list""" 95 | if brush_cycler.add_current_brush(): 96 | self.update_list() 97 | self.brushListChanged.emit() 98 | log(f"Added current brush to cycle list. Total: {len(brush_cycler.brush_list)}") 99 | else: 100 | log("Could not add current brush to cycle list") 101 | 102 | def remove_selected_brush(self): 103 | """Remove the selected brush from the list""" 104 | selected_items = self.brush_list.selectedItems() 105 | if not selected_items: 106 | return 107 | 108 | item = selected_items[0] 109 | brush_name = item.text() 110 | # Remove " (current)" suffix if present 111 | if " (current)" in brush_name: 112 | brush_name = brush_name.replace(" (current)", "") 113 | 114 | # Get the index of the selected item 115 | index = self.brush_list.row(item) 116 | 117 | # Remove the brush from the cycler 118 | if brush_cycler.remove_brush(brush_name): 119 | self.update_list() 120 | self.brushListChanged.emit() 121 | log(f"Removed brush '{brush_name}' from cycle list") 122 | else: 123 | log(f"Could not remove brush '{brush_name}' from cycle list") 124 | 125 | def move_brush_up(self): 126 | """Move the selected brush up in the list""" 127 | selected_items = self.brush_list.selectedItems() 128 | if not selected_items: 129 | return 130 | 131 | current_row = self.brush_list.row(selected_items[0]) 132 | if current_row > 0: 133 | # Get the brush list from the cycler 134 | brush_list = brush_cycler.brush_list.copy() 135 | 136 | # Swap the brushes 137 | brush_list[current_row], brush_list[current_row - 1] = brush_list[current_row - 1], brush_list[current_row] 138 | 139 | # Update the cycler's brush list 140 | brush_cycler.set_brush_list(brush_list) 141 | 142 | # Update the list widget 143 | self.update_list() 144 | 145 | # Select the moved item 146 | self.brush_list.setCurrentRow(current_row - 1) 147 | 148 | self.brushListChanged.emit() 149 | 150 | def move_brush_down(self): 151 | """Move the selected brush down in the list""" 152 | selected_items = self.brush_list.selectedItems() 153 | if not selected_items: 154 | return 155 | 156 | current_row = self.brush_list.row(selected_items[0]) 157 | if current_row < self.brush_list.count() - 1: 158 | # Get the brush list from the cycler 159 | brush_list = brush_cycler.brush_list.copy() 160 | 161 | # Swap the brushes 162 | brush_list[current_row], brush_list[current_row + 1] = brush_list[current_row + 1], brush_list[current_row] 163 | 164 | # Update the cycler's brush list 165 | brush_cycler.set_brush_list(brush_list) 166 | 167 | # Update the list widget 168 | self.update_list() 169 | 170 | # Select the moved item 171 | self.brush_list.setCurrentRow(current_row + 1) 172 | 173 | self.brushListChanged.emit() 174 | 175 | def clear_brush_list(self): 176 | """Clear the brush list after confirmation""" 177 | # Ask for confirmation 178 | reply = QMessageBox.question( 179 | self, 180 | "Clear Brush List", 181 | "Are you sure you want to clear the brush list?", 182 | QMessageBox.Yes | QMessageBox.No, 183 | QMessageBox.No 184 | ) 185 | 186 | if reply == QMessageBox.Yes: 187 | brush_cycler.clear_brush_list() 188 | self.update_list() 189 | self.brushListChanged.emit() 190 | log("Cleared brush cycle list") 191 | 192 | def on_rows_moved(self, parent, start, end, destination, row): 193 | """Handle rows being moved by drag and drop""" 194 | # Get the new order of brushes 195 | new_brush_list = [] 196 | for i in range(self.brush_list.count()): 197 | item = self.brush_list.item(i) 198 | brush_name = item.text() 199 | # Remove " (current)" suffix if present 200 | if " (current)" in brush_name: 201 | brush_name = brush_name.replace(" (current)", "") 202 | new_brush_list.append(brush_name) 203 | 204 | # Update the cycler's brush list 205 | brush_cycler.set_brush_list(new_brush_list) 206 | self.update_list() 207 | self.brushListChanged.emit() 208 | 209 | # Dialog to show the brush list widget 210 | class BrushListDialog(QDialog): 211 | """Dialog to display and edit the brush list""" 212 | 213 | def __init__(self, parent=None): 214 | super().__init__(parent) 215 | self.setWindowTitle("Brush Cycle List Editor") 216 | self.resize(300, 400) 217 | 218 | # Main layout 219 | layout = QVBoxLayout(self) 220 | 221 | # Add the brush list widget 222 | self.brush_list_widget = BrushListWidget(self) 223 | layout.addWidget(self.brush_list_widget) 224 | 225 | # Add standard dialog buttons 226 | button_box = QDialogButtonBox(QDialogButtonBox.Close) 227 | button_box.rejected.connect(self.reject) 228 | layout.addWidget(button_box) -------------------------------------------------------------------------------- /pykrita/recent_color/color_history_docker.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt, pyqtSignal, QRect 2 | from PyQt5.QtGui import QColor, QResizeEvent, QPen, QPainter 3 | from PyQt5.QtWidgets import QDockWidget, QWidget, QGridLayout, QLabel 4 | from krita import DockWidget, Krita 5 | from .recent_color import rgb, setFgColor, update_label_from_virtual_color, log, maybe_dry_paper_and_autoResetOpacity, log 6 | from . import globals as g 7 | from .brush_cycler import brush_cycler # Import the brush cycler instance 8 | 9 | 10 | # --- Custom Widget for Clickable Color Squares --- 11 | class ClickableColorLabel(QLabel): 12 | """ A QLabel that displays a color and emits a signal when clicked. """ 13 | clicked = pyqtSignal(QColor) 14 | 15 | def __init__(self, color, parent=None): 16 | self.is_selected = False 17 | super().__init__(parent) # Ensure QLabel is properly initialized 18 | self._color = color 19 | self.setFixedSize(32, 32) # Fixed size for the label 20 | self.setAutoFillBackground(True) 21 | self.set_background_color(self._color) 22 | self.setToolTip(f"Color: {self._color.name()}") 23 | 24 | def set_background_color(self, color): 25 | """ Update the background color of the label. """ 26 | palette = self.palette() 27 | palette.setColor(self.backgroundRole(), color) 28 | self.setPalette(palette) 29 | 30 | def get_color(self): 31 | return self._color 32 | 33 | def mousePressEvent(self, event): 34 | if event.button() == Qt.LeftButton: 35 | log(f"ClickableColorLabel clicked: emitting color {self._color.name()}") 36 | self.clicked.emit(self._color) 37 | super().mousePressEvent(event) 38 | 39 | def paintEvent(self, event): 40 | super().paintEvent(event) 41 | if self.is_selected: 42 | painter = QPainter(self) 43 | painter.setPen(QPen(QColor(255, 255, 255), 2)) # White rectangle with border thickness 2 44 | rect = QRect(0, 0, self.width(), self.height()) 45 | painter.drawRect(rect) 46 | 47 | def set_selected(self, selected): 48 | self.is_selected = selected 49 | 50 | # --- Color History Docker Definition --- 51 | class ColorHistoryDocker(DockWidget): 52 | color_squares = [] 53 | def __init__(self): 54 | super().__init__() 55 | g.g_color_history_docker_instance = self # Store instance globally 56 | self.setWindowTitle("ColorPlus Color History") 57 | mainWidget = QWidget(self) 58 | self.setWidget(mainWidget) 59 | 60 | # Use a grid layout for multi-row display 61 | self.color_history_layout = QGridLayout() 62 | self.color_history_layout.setContentsMargins(5, 5, 5, 5) 63 | self.color_history_layout.setSpacing(2) # Spacing between squares 64 | mainWidget.setLayout(self.color_history_layout) 65 | 66 | 67 | # Initial UI population 68 | self.update_color_history_ui() 69 | 70 | def update_color_history_ui(self) -> None: 71 | """ Clears and rebuilds the color history UI display in a grid layout. """ 72 | # Clear existing widgets from the layout 73 | while self.color_history_layout.count() > 0: 74 | item = self.color_history_layout.takeAt(0) 75 | widget = item.widget() 76 | if widget: 77 | widget.deleteLater() 78 | self.color_squares = [] # Clear the list of color squares 79 | 80 | # Add new color squares in a grid layout 81 | # log(f"Updating color history UI with {len(g.g_last_virtual_colors_used)} colors.") 82 | 83 | # Calculate how many color squares can fit in the current width 84 | docker_width = self.width() 85 | square_size = 32 # Size of each color square 86 | spacing = 2 # Spacing between squares 87 | margins = 10 # Total horizontal margins (5px on each side) 88 | 89 | # Calculate available width and how many squares can fit 90 | available_width = docker_width - margins 91 | colors_per_row = max(1, available_width // (square_size + spacing)) 92 | # log(f"Docker width: {docker_width}px, can fit {colors_per_row} color squares per row") 93 | 94 | for i, item in enumerate(g.g_last_virtual_colors_used): 95 | qcolor_to_display = None 96 | if hasattr(item, 'r') and hasattr(item, 'g') and hasattr(item, 'b'): # Check if it's our custom rgb class 97 | # Convert rgb object to QColor, ensuring values are integers 98 | try: 99 | r_val = int(round(item.b)) # inverto perche' in realta' la mia immagine e' bgr 100 | g_val = int(round(item.g)) 101 | b_val = int(round(item.r)) 102 | # Clamp values to 0-255 just in case 103 | r_val = max(0, min(255, r_val)) 104 | g_val = max(0, min(255, g_val)) 105 | b_val = max(0, min(255, b_val)) 106 | qcolor_to_display = QColor(r_val, g_val, b_val) 107 | except Exception as e: 108 | log(f"Error converting rgb to QColor: {e}, rgb values: r={item.r}, g={item.g}, b={item.b}") 109 | elif isinstance(item, QColor): # Handle if it's already a QColor 110 | qcolor_to_display = item 111 | else: 112 | log(f"Warning: Item in g_last_virtual_colors_used is not an rgb or QColor object: {type(item)}") 113 | 114 | if qcolor_to_display: 115 | color_square = ClickableColorLabel(qcolor_to_display) # Pass the QColor 116 | color_square.clicked.connect(self._on_color_square_clicked) 117 | self.color_squares.append(color_square) # Store the color square 118 | 119 | # Calculate row and column for grid layout 120 | row = i // colors_per_row 121 | col = i % colors_per_row 122 | self.color_history_layout.addWidget(color_square, row, col) 123 | 124 | # Update selection after all squares are added 125 | for i, square in enumerate(self.color_squares): 126 | square.set_selected(i == g.g_color_history_index) 127 | # self.update_rectangle_position() # No longer needed 128 | 129 | # --- Slot for Color Square Clicks --- 130 | def _on_color_square_clicked(self, color) -> None: 131 | 132 | if brush_cycler.enabled: 133 | g.g_brushCyclerIndex = 0 134 | brush_cycler.apply_current_brush() 135 | 136 | 137 | log("resettato index brush cycle") 138 | 139 | 140 | """ Handles clicks on the color history squares. """ 141 | log(f"Color square clicked: {color.name()}") 142 | # Set as foreground color in Krita 143 | 144 | 145 | clickedColorRgb = rgb(float(color.blueF() * 255.0) , float( color.greenF() * 255.0) , float( color.redF() * 255.0) , 255.0) 146 | g.g_virtual_fg_color_rgb = clickedColorRgb 147 | update_label_from_virtual_color() 148 | 149 | g.g_ColorOnTopOfHistoryIsTemp = False # cambiando colore, implicitamente hai accettato il colore temporaneo automix 150 | 151 | # invece di setFgColor(g.g_virtual_fg_color_rgb) 152 | # faccio cosi', altrimenti per sottili differenze di arrotondamento si creano duplicati nella history 153 | view = Krita.instance().activeWindow().activeView() 154 | fg = view.foregroundColor() 155 | comp = fg.components() 156 | if len(comp) < 3: 157 | log(f"non posso settare come fg color di krita questo colore, perche' attualmente sei su un layer grayscale. il fg color ha questa struttura {comp}") 158 | else: 159 | comp[0] = color.blueF() 160 | comp[1] = color.greenF() 161 | comp[2] = color.redF() 162 | fg.setComponents(comp) 163 | view.setForeGroundColor(fg) 164 | 165 | 166 | 167 | maybe_dry_paper_and_autoResetOpacity() 168 | 169 | def canvasChanged(self, canvas): 170 | """ Override of the abstract method from DockWidget class. 171 | Called when the canvas changes in Krita. """ 172 | # log(f"Canvas changed in ColorHistoryDocker") 173 | # Update the UI when canvas changes 174 | # self.update_color_history_ui() # commentato perche lo fa ad ogni stroke 175 | 176 | def resizeEvent(self, event: QResizeEvent): 177 | """ Called when the docker widget is resized. """ 178 | log(f"ColorHistoryDocker resized to: {event.size().width()}x{event.size().height()}") 179 | # Update the layout and recalculate positions on resize 180 | self.update_color_history_ui() # Recalculate layout 181 | 182 | # Call the base class implementation 183 | super().resizeEvent(event) # Call base class implementation -------------------------------------------------------------------------------- /pykrita/recent_color/globals.py: -------------------------------------------------------------------------------- 1 | from typing import List, TYPE_CHECKING, Optional, Dict # Import List, TYPE_CHECKING, Optional, and Dict 2 | 3 | 4 | 5 | if TYPE_CHECKING: 6 | from .recent_color import rgb # Import for type hinting only 7 | from PyQt5.QtWidgets import QLabel # Import for type hinting only 8 | from .brush_cycler import BrushCycler # Import for type hinting only 9 | 10 | # Funzione di log per il debug 11 | def log(s): 12 | global printCount 13 | printCount += 1 14 | print(f"{printCount}: {s}\n\n") 15 | 16 | g_blur_on_dry = False 17 | 18 | countColorChanged = 0 19 | printCount = 0 20 | 21 | 22 | last_log_time_sample_points = 0 23 | last_log_time_sampled_colors = 0 24 | last_log_time_final_color = 0 25 | 26 | g_mouse_is_out_of_canvas = True 27 | 28 | # g_mix_auto_clears_cur_layer = "1" 29 | 30 | g_brushCyclerIndex = 0 31 | 32 | g_ColorOnTopOfHistoryIsTemp : bool = False 33 | 34 | g_color_changed_since_last_leave = False # Tracks if color changed after leaving canvas 35 | 36 | g_layer_is_dirty: dict[str, bool] = {} # Use lowercase dict type hint 37 | 38 | g_diminishing_opacity = False #True to have auto-mixing with amount that auto-decreases 39 | 40 | #g_btn_pick_color = None 41 | 42 | 43 | 44 | #g_virtual_color_used_last_rgb = None 45 | 46 | # questo colore e' il target per l'automix, e se c'e' dirty brush e' il colore originale prima di sporcarsi 47 | g_virtual_fg_color_rgb : Optional['rgb'] = None 48 | 49 | g_last_virtual_colors_used: List['rgb'] = [] # Add type hint using forward reference 50 | g_color_history_index = -1 # Index pointing to the 'active' color in g_last_virtual_colors_used for switching 51 | 52 | timeMessage = 300 53 | g_normal_step_layer_opacity = 20 54 | 55 | g_mixing_step = 0.05 56 | 57 | g_actionAutoMix = None 58 | g_actionDirtyBrush = None 59 | g_actionBrushCycler = None 60 | g_actionAutoFocus = None 61 | g_actionAutoResOnPick = None 62 | g_actionSingleLayerMode = None 63 | g_manualResOnPick = None 64 | 65 | g_auto_mixing_distance_step = 5 66 | 67 | g_set_spectral_blend_mode_when_creating_layer = False 68 | 69 | g_multi_layer_mode = False 70 | 71 | ###############auto-mixing 72 | 73 | g_auto_mixing_uses_distance_logic = False # perche' io posso prendere un colore molto diverso o molto simile. voglio contrastare sempre poco, mai tanto. altrimenti non attivo l'auto mixing. 74 | # d'altra parta, è sbagliato concettualmente se io fisso un target color e voglio arrivarci in N strokes 75 | g_auto_mixing_just_once_logic = False 76 | g_auto_mixing_just_once_now_on = False 77 | 78 | 79 | 80 | #g_ultimo_colore_vero_settato_dall_utente = None 81 | 82 | #when distance logic is active 83 | g_auto_mixing_target_distance = None # value is ignored, will be read from settings. 84 | 85 | #when distance logic is not active 86 | g_auto_mix__how_much_canvas_to_pick : float = 0.5 # value is ignored: will be read from settings --- 0.999 to drag color from canvas , e.g. to remove overlap. then set auto-mixing. 87 | 88 | g_auto_mix_ignore_current_layer = False # metti false se vuoi trascinare il colore appena messo, true se vuoi evitarlo 89 | 90 | #g_auto_mix_snap_distance = 30 91 | 92 | ######################## 93 | 94 | g_auto_opacity_max_distance = 40 95 | 96 | g_auto_dry_each_stroke = False 97 | g_is_drying_paper = False # Flag to prevent history update during dryPaper 98 | 99 | g_auto_mix_paused = False 100 | g_auto_mix_enabled = False 101 | 102 | g_auto_reset_opacity_on_pick : bool = False 103 | g_auto_reset_opacity_on_pick_level = 1.0; 104 | 105 | g_dial_auto_mix_level = None 106 | 107 | g_dirty_brush_overall_enabled = False 108 | g_dirty_brush_currently_on = True 109 | 110 | 111 | # TODO probabilmente questi sono la stessa variabile, togline una 112 | g_dirty_brush_latest_dirty_color_for_automix : Optional['rgb'] = None 113 | g_dirty_brush_color_to_ignore : Optional[list[float] ] = None 114 | 115 | 116 | g_dirty_brush_level = None # value is ignored: will be read from settings --- range from 0.04 to 0.5 117 | g_mix_radius : Optional[float] = None # value is ignored: will be read from settings --- range from 0 to 20 pixels 118 | g_mix_radius_enabled = False 119 | 120 | g_color_on_down_dirty_brush: Optional['rgb'] = None # Add type hint 121 | g_last_coord_mouse_down = None 122 | g_last_coord_mouse_up = None 123 | 124 | #g_picking_color = False 125 | #g_mixing_color = False 126 | 127 | 128 | g_virtual_fg_color_rgb_previous_when_dirty_brush_on :Optional['rgb']= None 129 | 130 | g_opacity_decided_for_layer = False 131 | 132 | lblActiveColor : 'QLabel' = None # Use None, forward reference QLabel 133 | 134 | event_lookup = {"0": "QEvent::None", 135 | "114": "QEvent::ActionAdded", 136 | "113": "QEvent::ActionChanged", 137 | "115": "QEvent::ActionRemoved", 138 | "99": "QEvent::ActivationChange", 139 | "121": "QEvent::ApplicationActivate", 140 | "122": "QEvent::ApplicationDeactivate", 141 | "36": "QEvent::ApplicationFontChange", 142 | "37": "QEvent::ApplicationLayoutDirectionChange", 143 | "38": "QEvent::ApplicationPaletteChange", 144 | "214": "QEvent::ApplicationStateChange", 145 | "35": "QEvent::ApplicationWindowIconChange", 146 | "68": "QEvent::ChildAdded", 147 | "69": "QEvent::ChildPolished", 148 | "71": "QEvent::ChildRemoved", 149 | "40": "QEvent::Clipboard", 150 | "19": "QEvent::Close", 151 | "200": "QEvent::CloseSoftwareInputPanel", 152 | "178": "QEvent::ContentsRectChange", 153 | "82": "QEvent::ContextMenu", 154 | "183": "QEvent::CursorChange", 155 | "52": "QEvent::DeferredDelete", 156 | "60": "QEvent::DragEnter", 157 | "62": "QEvent::DragLeave", 158 | "61": "QEvent::DragMove", 159 | "63": "QEvent::Drop", 160 | "170": "QEvent::DynamicPropertyChange", 161 | "98": "QEvent::EnabledChange", 162 | "10": "QEvent::Enter", 163 | "150": "QEvent::EnterEditFocus", 164 | "124": "QEvent::EnterWhatsThisMode", 165 | "206": "QEvent::Expose", 166 | "116": "QEvent::FileOpen", 167 | "8": "QEvent::FocusIn", 168 | "9": "QEvent::FocusOut", 169 | "23": "QEvent::FocusAboutToChange", 170 | "97": "QEvent::FontChange", 171 | "198": "QEvent::Gesture", 172 | "202": "QEvent::GestureOverride", 173 | "188": "QEvent::GrabKeyboard", 174 | "186": "QEvent::GrabMouse", 175 | "159": "QEvent::GraphicsSceneContextMenu", 176 | "164": "QEvent::GraphicsSceneDragEnter", 177 | "166": "QEvent::GraphicsSceneDragLeave", 178 | "165": "QEvent::GraphicsSceneDragMove", 179 | "167": "QEvent::GraphicsSceneDrop", 180 | "163": "QEvent::GraphicsSceneHelp", 181 | "160": "QEvent::GraphicsSceneHoverEnter", 182 | "162": "QEvent::GraphicsSceneHoverLeave", 183 | "161": "QEvent::GraphicsSceneHoverMove", 184 | "158": "QEvent::GraphicsSceneMouseDoubleClick", 185 | "155": "QEvent::GraphicsSceneMouseMove", 186 | "156": "QEvent::GraphicsSceneMousePress", 187 | "157": "QEvent::GraphicsSceneMouseRelease", 188 | "182": "QEvent::GraphicsSceneMove", 189 | "181": "QEvent::GraphicsSceneResize", 190 | "168": "QEvent::GraphicsSceneWheel", 191 | "18": "QEvent::Hide", 192 | "27": "QEvent::HideToParent", 193 | "127": "QEvent::HoverEnter", 194 | "128": "QEvent::HoverLeave", 195 | "129": "QEvent::HoverMove", 196 | "96": "QEvent::IconDrag", 197 | "101": "QEvent::IconTextChange", 198 | "83": "QEvent::InputMethod", 199 | "207": "QEvent::InputMethodQuery", 200 | "169": "QEvent::KeyboardLayoutChange", 201 | "6": "QEvent::KeyPress", 202 | "7": "QEvent::KeyRelease", 203 | "89": "QEvent::LanguageChange", 204 | "90": "QEvent::LayoutDirectionChange", 205 | "76": "QEvent::LayoutRequest", 206 | "11": "QEvent::Leave", 207 | "151": "QEvent::LeaveEditFocus", 208 | "125": "QEvent::LeaveWhatsThisMode", 209 | "88": "QEvent::LocaleChange", 210 | "176": "QEvent::NonClientAreaMouseButtonDblClick", 211 | "174": "QEvent::NonClientAreaMouseButtonPress", 212 | "175": "QEvent::NonClientAreaMouseButtonRelease", 213 | "173": "QEvent::NonClientAreaMouseMove", 214 | "177": "QEvent::MacSizeChange", 215 | "43": "QEvent::MetaCall", 216 | "102": "QEvent::ModifiedChange", 217 | "4": "QEvent::MouseButtonDblClick", 218 | "2": "QEvent::MouseButtonPress", 219 | "3": "QEvent::MouseButtonRelease", 220 | "5": "QEvent::MouseMove", 221 | "109": "QEvent::MouseTrackingChange", 222 | "13": "QEvent::Move", 223 | "197": "QEvent::NativeGesture", 224 | "208": "QEvent::OrientationChange", 225 | "12": "QEvent::Paint", 226 | "39": "QEvent::PaletteChange", 227 | "131": "QEvent::ParentAboutToChange", 228 | "21": "QEvent::ParentChange", 229 | "212": "QEvent::PlatformPanel", 230 | "217": "QEvent::PlatformSurface", 231 | "75": "QEvent::Polish", 232 | "74": "QEvent::PolishRequest", 233 | "123": "QEvent::QueryWhatsThis", 234 | "106": "QEvent::ReadOnlyChange", 235 | "199": "QEvent::RequestSoftwareInputPanel", 236 | "14": "QEvent::Resize", 237 | "204": "QEvent::ScrollPrepare", 238 | "205": "QEvent::Scroll", 239 | "117": "QEvent::Shortcut", 240 | "51": "QEvent::ShortcutOverride", 241 | "17": "QEvent::Show", 242 | "26": "QEvent::ShowToParent", 243 | "50": "QEvent::SockAct", 244 | "192": "QEvent::StateMachineSignal", 245 | "193": "QEvent::StateMachineWrapped", 246 | "112": "QEvent::StatusTip", 247 | "100": "QEvent::StyleChange", 248 | "87": "QEvent::TabletMove", 249 | "92": "QEvent::TabletPress", 250 | "93": "QEvent::TabletRelease", 251 | "171": "QEvent::TabletEnterProximity", 252 | "172": "QEvent::TabletLeaveProximity", 253 | "219": "QEvent::TabletTrackingChange", 254 | "22": "QEvent::ThreadChange", 255 | "1": "QEvent::Timer", 256 | "120": "QEvent::ToolBarChange", 257 | "110": "QEvent::ToolTip", 258 | "184": "QEvent::ToolTipChange", 259 | "194": "QEvent::TouchBegin", 260 | "209": "QEvent::TouchCancel", 261 | "196": "QEvent::TouchEnd", 262 | "195": "QEvent::TouchUpdate", 263 | "189": "QEvent::UngrabKeyboard", 264 | "187": "QEvent::UngrabMouse", 265 | "78": "QEvent::UpdateLater", 266 | "77": "QEvent::UpdateRequest", 267 | "111": "QEvent::WhatsThis", 268 | "118": "QEvent::WhatsThisClicked", 269 | "31": "QEvent::Wheel", 270 | "132": "QEvent::WinEventAct", 271 | "24": "QEvent::WindowActivate", 272 | "103": "QEvent::WindowBlocked", 273 | "25": "QEvent::WindowDeactivate", 274 | "34": "QEvent::WindowIconChange", 275 | "105": "QEvent::WindowStateChange", 276 | "33": "QEvent::WindowTitleChange", 277 | "104": "QEvent::WindowUnblocked", 278 | "203": "QEvent::WinIdChange", 279 | "126": "QEvent::ZOrderChange", } 280 | 281 | allBrushPresets = None # Use None instead of null 282 | 283 | # Brush cycler instance 284 | g_brush_cycler_instance = None # Will be set to BrushCycler instance 285 | g_btn_brush_cycler = None # Button for brush cycler 286 | g_btn_auto_focus = None # Button for autofocus windows 287 | g_btn_auto_reset_opacity = None # Button for auto-reset layer opacity 288 | g_btn_single_layer = None # Button for single-layer mode 289 | 290 | g_docker_instance = None 291 | g_color_history_docker_instance = None 292 | 293 | g_auto_focus = "False" 294 | 295 | # Dictionary to store window maximization state before entering fullscreen 296 | # Key: window title, Value: True if window was maximized, False otherwise 297 | g_window_maximized_states: Dict[str, bool] = {} -------------------------------------------------------------------------------- /pykrita/recent_color/mainDocker.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import ( 2 | Qt, 3 | QObject, 4 | QEvent, 5 | QPointF, 6 | QRect, 7 | QTimer, 8 | pyqtSignal) # Added pyqtSignal 9 | from . import globals as g 10 | from PyQt5.QtGui import ( 11 | QTransform, 12 | QPainter, 13 | QImage, 14 | QBrush, 15 | QIcon, 16 | QColor, 17 | QPolygonF, 18 | QInputEvent, 19 | QKeyEvent, 20 | QCursor) 21 | 22 | from .slider import KritaStyleSlider # Import the new slider 23 | 24 | from PyQt5.QtWidgets import ( 25 | QWidget, 26 | QMdiArea, 27 | QTextEdit, 28 | QAbstractScrollArea, 29 | QAction, QMenu, 30 | QLabel, # Added QLabel 31 | QHBoxLayout, # Added QHBoxLayout 32 | # Added QVBoxLayout (already used but good to be explicit) 33 | QVBoxLayout, 34 | # Added QPushButton (already used but good to be explicit) 35 | QPushButton, 36 | QDockWidget # Added QDockWidget back 37 | ) 38 | 39 | 40 | from krita import ( 41 | Krita, ManagedColor, Extension, DockWidget) 42 | 43 | 44 | from .recent_color import dryPaper, toggleAutoFocus, toggleDirtyBrush, toggleAutoMixing, toggleAutoResetOpacityOnPick,updateAutoResetOpacityLevel, toggleMultiLayerMode, mergeCleanup, quickMessage, brush_cycler, BrushListDialog 45 | 46 | class HelloDocker(DockWidget): 47 | def __init__(self): 48 | super().__init__() 49 | g.g_docker_instance = self # Store instance globally 50 | self.setWindowTitle("ColorPlus") 51 | mainWidget = QWidget(self) 52 | self.setWidget(mainWidget) 53 | 54 | mainLayout = QVBoxLayout() 55 | mainWidget.setLayout(mainLayout) 56 | 57 | # Color History has been moved to a separate docker 58 | 59 | # active color 60 | 61 | layoutHorizColorAndDry = QHBoxLayout() 62 | 63 | 64 | mainLayout.addLayout(layoutHorizColorAndDry) 65 | 66 | 67 | g.lblActiveColor = QLabel() 68 | g.lblActiveColor.setToolTip("Current foreground color") 69 | layoutHorizColorAndDry.addWidget(g.lblActiveColor) 70 | # lblActiveColor.setStyleSheet("background-color: red") 71 | g.lblActiveColor.setMinimumHeight(45) 72 | g.lblActiveColor.setMinimumWidth(65) 73 | 74 | 75 | 76 | 77 | btnDry = QPushButton("Dry paper", mainWidget) 78 | layoutHorizColorAndDry.addWidget(btnDry) 79 | font = btnDry.font() 80 | font.setPixelSize(15) 81 | btnDry.setFont(font) 82 | btnDry.setMinimumHeight(50) 83 | 84 | btnDry.clicked.connect(lambda: dryPaper()) 85 | 86 | 87 | # # mix layout 88 | layoutHorizMix = QHBoxLayout() 89 | mainLayout.addLayout(layoutHorizMix) 90 | 91 | 92 | g.g_slider_mix = KritaStyleSlider(mainWidget, "Mix level") 93 | g.g_slider_mix.setToolTip("Mix level") 94 | layoutHorizMix.addWidget(g.g_slider_mix) 95 | # Set initial value (0.0-1.0 range) 96 | g.g_slider_mix.setValue(g.g_how_much_canvas_to_pick) 97 | g.g_slider_mix.valueChanged.connect(self.mixLevelValueChanged) 98 | 99 | # auto-mix layout 100 | 101 | layoutHorizAutoMix = QHBoxLayout() 102 | mainLayout.addLayout(layoutHorizAutoMix) 103 | 104 | # auto-mix button 105 | 106 | g.g_btn_auto_mix = QPushButton("Auto-mix color", mainWidget) 107 | g.g_btn_auto_mix.setCheckable(True) 108 | layoutHorizAutoMix.addWidget(g.g_btn_auto_mix) 109 | g.g_btn_auto_mix.clicked.connect(toggleAutoMixing) 110 | # g.g_btn_auto_mix.setMinimumHeight(60) 111 | 112 | font = g.g_btn_auto_mix.font() 113 | font.setPixelSize(15) 114 | g.g_btn_auto_mix.setFont(font) 115 | 116 | # auto-mix level 117 | 118 | g.g_slider_auto_mix_level = KritaStyleSlider(mainWidget, "Auto-mix level") 119 | g.g_slider_auto_mix_level.setToolTip("Auto-mix level") 120 | layoutHorizAutoMix.addWidget(g.g_slider_auto_mix_level) 121 | # Set initial value (0.0-1.0 range) 122 | g.g_slider_auto_mix_level.setValue(g.g_auto_mix__how_much_canvas_to_pick) 123 | g.g_slider_auto_mix_level.valueChanged.connect( 124 | self.autoMixLevelValueChanged) 125 | g.g_slider_auto_mix_level.setEnabled(g.g_auto_mix_enabled) 126 | 127 | # dirty brush layout 128 | layoutHorizDirtyBrush = QHBoxLayout() 129 | mainLayout.addLayout(layoutHorizDirtyBrush) 130 | 131 | # dirty brush button 132 | g.g_btn_dirty_brush = QPushButton("Dirty brush", mainWidget) 133 | g.g_btn_dirty_brush.setCheckable(True) 134 | layoutHorizDirtyBrush.addWidget(g.g_btn_dirty_brush) 135 | g.g_btn_dirty_brush.clicked.connect(toggleDirtyBrush) 136 | # g.g_btn_dirty_brush.setMinimumHeight(60) 137 | 138 | font = g.g_btn_dirty_brush.font() 139 | font.setPixelSize(15) 140 | g.g_btn_dirty_brush.setFont(font) 141 | 142 | # dirty brush level 143 | g.g_slider_dirty_brush_level = KritaStyleSlider(mainWidget, "Dirty brush level") 144 | g.g_slider_dirty_brush_level.setToolTip("Dirty brush level") 145 | layoutHorizDirtyBrush.addWidget(g.g_slider_dirty_brush_level) 146 | # Set initial value (map 0.04-0.5 range to 0.0-1.0) 147 | initial_dirty_value = (g.g_dirty_brush_level - 0.04) / 0.46 148 | g.g_slider_dirty_brush_level.setValue(max(0.0, min(1.0, initial_dirty_value))) # Clamp value 149 | g.g_slider_dirty_brush_level.valueChanged.connect( 150 | self.dirtyBrushLevelValueChanged) 151 | g.g_slider_dirty_brush_level.setEnabled(g.g_dirty_brush_overall_enabled) 152 | 153 | # mix radius layout 154 | layoutHorizMixRadius = QHBoxLayout() 155 | mainLayout.addLayout(layoutHorizMixRadius) 156 | 157 | # mix radius button (replacing static label) 158 | g.g_btn_mix_radius = QPushButton("Mix radius", mainWidget) 159 | g.g_btn_mix_radius.setCheckable(True) 160 | g.g_btn_mix_radius.setChecked(g.g_mix_radius_enabled) 161 | layoutHorizMixRadius.addWidget(g.g_btn_mix_radius) 162 | font = g.g_btn_mix_radius.font() 163 | font.setPixelSize(15) 164 | g.g_btn_mix_radius.setFont(font) 165 | # g.g_btn_mix_radius.setMinimumHeight(60) 166 | g.g_btn_mix_radius.clicked.connect(self.toggleMixRadiusEnabled) 167 | 168 | # Autofocus windows layout 169 | layoutHorizAutoFocus = QHBoxLayout() 170 | mainLayout.addLayout(layoutHorizAutoFocus) 171 | 172 | # Autofocus windows button 173 | g.g_btn_auto_focus = QPushButton("Autofocus windows", mainWidget) 174 | g.g_btn_auto_focus.setCheckable(True) 175 | g.g_btn_auto_focus.setChecked(g.g_auto_focus == "true") 176 | layoutHorizAutoFocus.addWidget(g.g_btn_auto_focus) 177 | font = g.g_btn_auto_focus.font() 178 | font.setPixelSize(15) 179 | g.g_btn_auto_focus.setFont(font) 180 | g.g_btn_auto_focus.setToolTip("Autofocus windows on mouse over") 181 | g.g_btn_auto_focus.clicked.connect(toggleAutoFocus) 182 | 183 | # Auto-reset layer opacity layout 184 | layoutHorizAutoResetOpacity = QHBoxLayout() 185 | mainLayout.addLayout(layoutHorizAutoResetOpacity) 186 | 187 | # Auto-reset layer opacity button 188 | g.g_btn_auto_reset_opacity = QPushButton("Auto-reset opacity", mainWidget) 189 | g.g_btn_auto_reset_opacity.setCheckable(True) 190 | g.g_btn_auto_reset_opacity.setChecked(g.g_auto_reset_opacity_on_pick == 1) 191 | layoutHorizAutoResetOpacity.addWidget(g.g_btn_auto_reset_opacity) 192 | font = g.g_btn_auto_reset_opacity.font() 193 | font.setPixelSize(15) 194 | g.g_btn_auto_reset_opacity.setFont(font) 195 | g.g_btn_auto_reset_opacity.setToolTip("Auto-reset layer opacity to default on color pick") 196 | g.g_btn_auto_reset_opacity.clicked.connect(toggleAutoResetOpacityOnPick) 197 | 198 | # Auto-reset layer opacity slider 199 | g.g_slider_auto_reset_opacity = KritaStyleSlider( mainWidget, "Auto-reset opacity") 200 | # g.g_slider_auto_reset_opacity.setRange(0, 100) 201 | g.g_slider_auto_reset_opacity.setValue(g.g_auto_reset_opacity_on_pick_level / 100.0) 202 | # g.g_slider_auto_reset_opacity.setToolTip(f"Set default opacity level ({int(g.g_auto_reset_opacity_on_pick_level * 100.0)}%)") 203 | g.g_slider_auto_reset_opacity.valueChanged.connect(updateAutoResetOpacityLevel) 204 | # g.g_slider_auto_reset_opacity.setMinimumWidth(80) # Give it some minimum width 205 | layoutHorizAutoResetOpacity.addWidget(g.g_slider_auto_reset_opacity) 206 | 207 | g.g_slider_auto_reset_opacity.setEnabled(g.g_auto_reset_opacity_on_pick) 208 | # Add stretch to push button and slider to the left 209 | # layoutHorizAutoResetOpacity.addStretch(1) 210 | 211 | # Single-layer mode layout 212 | layoutHorizSingleLayer = QHBoxLayout() 213 | mainLayout.addLayout(layoutHorizSingleLayer) 214 | 215 | # Single-layer mode button 216 | g.g_btn_single_layer = QPushButton("Single-layer mode", mainWidget) 217 | g.g_btn_single_layer.setCheckable(True) 218 | g.g_btn_single_layer.setChecked(not g.g_multi_layer_mode) 219 | layoutHorizSingleLayer.addWidget(g.g_btn_single_layer) 220 | font = g.g_btn_single_layer.font() 221 | font.setPixelSize(15) 222 | g.g_btn_single_layer.setFont(font) 223 | g.g_btn_single_layer.setToolTip("Single-layer mode (don't auto create layers for watercolor effect)") 224 | g.g_btn_single_layer.clicked.connect(toggleMultiLayerMode) 225 | 226 | # Cleanup layers layout 227 | layoutHorizCleanup = QHBoxLayout() 228 | mainLayout.addLayout(layoutHorizCleanup) 229 | 230 | # Cleanup layers button 231 | btnCleanup = QPushButton("Cleanup layers", mainWidget) 232 | layoutHorizCleanup.addWidget(btnCleanup) 233 | font = btnCleanup.font() 234 | font.setPixelSize(15) 235 | btnCleanup.setFont(font) 236 | btnCleanup.setToolTip("Merge all temporary layers") 237 | # btnCleanup.setMinimumHeight(50) 238 | btnCleanup.clicked.connect(mergeCleanup) 239 | 240 | # mix radius dial 241 | g.g_slider_mix_radius = KritaStyleSlider(mainWidget, "Mix radius") 242 | g.g_slider_mix_radius.setToolTip("Mix radius in pixels (0-20)") 243 | layoutHorizMixRadius.addWidget(g.g_slider_mix_radius) 244 | # Set initial value (map 0-20 range to 0.0-1.0) 245 | initial_radius_value = g.g_mix_radius / 20.0 if g.g_mix_radius is not None else 0.0 246 | g.g_slider_mix_radius.setValue(max(0.0, min(1.0, initial_radius_value))) # Clamp value 247 | g.g_slider_mix_radius.valueChanged.connect(self.mixRadiusValueChanged) 248 | 249 | g.g_slider_mix_radius.setEnabled(g.g_mix_radius_enabled) 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | # per ora commento il brush cycler, non e' chiaro lo scopo 259 | # Brush cycler layout 260 | layoutHorizBrushCycler = QHBoxLayout() 261 | mainLayout.addLayout(layoutHorizBrushCycler) 262 | 263 | # Brush cycler button 264 | g.g_btn_brush_cycler = QPushButton("Auto-cycle brushes", mainWidget) 265 | g.g_btn_brush_cycler.setCheckable(True) 266 | layoutHorizBrushCycler.addWidget(g.g_btn_brush_cycler) 267 | g.g_btn_brush_cycler.clicked.connect(self.toggleBrushCycler) 268 | # g.g_btn_brush_cycler.setMinimumHeight(60) 269 | 270 | # Set initial tooltip 271 | if brush_cycler.brush_list: 272 | g.g_btn_brush_cycler.setToolTip(f"Cycle brushes (enabled: {brush_cycler.enabled}, {len(brush_cycler.brush_list)} brushes)") 273 | else: 274 | g.g_btn_brush_cycler.setToolTip("Cycle brushes (no brushes in list)") 275 | 276 | # font = g.g_btn_brush_cycler.font() 277 | # # font.setPixelSize(14) 278 | # g.g_btn_brush_cycler.setFont(font) 279 | 280 | # Button to add current brush to cycle list 281 | btnAddBrush = QPushButton("+", mainWidget) 282 | btnAddBrush.setToolTip("Add current brush to cycle list") 283 | layoutHorizBrushCycler.addWidget(btnAddBrush) 284 | btnAddBrush.clicked.connect(self.addCurrentBrushToCycleList) 285 | # btnAddBrush.setMinimumHeight(60) 286 | btnAddBrush.setMaximumWidth(40) 287 | 288 | # Button to edit brush list 289 | btnEditBrushList = QPushButton("Edit List", mainWidget) 290 | btnEditBrushList.setToolTip("View and edit brush cycle list") 291 | layoutHorizBrushCycler.addWidget(btnEditBrushList) 292 | btnEditBrushList.clicked.connect(self.showBrushListEditor) 293 | 294 | 295 | def leaveEvent(self, event): 296 | pass 297 | # log("Mouse left the dock widget") 298 | 299 | # label = QLabel("Hello", self) 300 | # self.setWidget(label) 301 | # self.label = label 302 | 303 | def autoMixLevelValueChanged(self, value): # Changed 'level' to 'value' (float 0.0-1.0) 304 | # log(f"autoMixLevelValueChanged {value}") 305 | 306 | g.g_auto_mix__how_much_canvas_to_pick = value # Use the float value directly 307 | 308 | Krita.instance().writeSetting("colorPlus", "g.g_auto_mix__how_much_canvas_to_pick", 309 | str(g.g_auto_mix__how_much_canvas_to_pick)) 310 | 311 | quickMessage( 312 | f"Changed auto-mixing to {round(g.g_auto_mix__how_much_canvas_to_pick * 100.0)} %") 313 | 314 | def dirtyBrushLevelValueChanged(self, value): # Changed 'level' to 'value' (float 0.0-1.0) 315 | # Convert slider value (0.0-1.0) to the desired range (0.04-0.5) 316 | g.g_dirty_brush_level = 0.04 + value * 0.46 317 | 318 | Krita.instance().writeSetting("colorPlus", "g.g_dirty_brush_level", 319 | str(g.g_dirty_brush_level)) 320 | 321 | quickMessage( 322 | f"Changed dirty brush level to {round(g.g_dirty_brush_level * 100.0)} %") 323 | 324 | def mixRadiusValueChanged(self, value): # Changed 'level' to 'value' (float 0.0-1.0) 325 | # Convert slider value (0.0-1.0) to the desired range (0-20 pixels) 326 | g.g_mix_radius = value * 20.0 327 | 328 | Krita.instance().writeSetting("colorPlus", "g.g_mix_radius", 329 | str(g.g_mix_radius)) 330 | 331 | quickMessage( 332 | f"Changed mix radius to {round(g.g_mix_radius)} pixels") 333 | 334 | def toggleMixRadiusEnabled(self): 335 | # Inverte lo stato della variabile g_mix_radius_enabled 336 | g.g_mix_radius_enabled = not g.g_mix_radius_enabled 337 | 338 | g.g_slider_mix_radius.setEnabled(g.g_mix_radius_enabled) 339 | 340 | # Aggiorna lo stato del pulsante 341 | g.g_btn_mix_radius.setChecked(g.g_mix_radius_enabled) 342 | 343 | # g.g_slid.setEnabled(True) 344 | 345 | # Salva lo stato nelle impostazioni di Krita per renderlo persistente 346 | Krita.instance().writeSetting("colorPlus", "g.g_mix_radius_enabled", 347 | "1" if g.g_mix_radius_enabled else "0") 348 | 349 | # Mostra un messaggio all'utente 350 | quickMessage(f"{'Enabled' if g.g_mix_radius_enabled else 'Disabled'} mix radius") 351 | 352 | 353 | def mixLevelValueChanged(self, value): # Changed 'level' to 'value' (float 0.0-1.0) 354 | 355 | g.g_how_much_canvas_to_pick = value # Use the float value directly 356 | 357 | Krita.instance().writeSetting("colorPlus", "g.g_how_much_canvas_to_pick", 358 | str(g.g_how_much_canvas_to_pick)) 359 | 360 | quickMessage( 361 | f"Changed mixing level to {round(g.g_how_much_canvas_to_pick * 100.0)} %") 362 | 363 | def toggleBrushCycler(self): 364 | """Toggle brush cycling on/off""" 365 | is_enabled = brush_cycler.toggle_enabled() 366 | g.g_btn_brush_cycler.setChecked(is_enabled) 367 | 368 | # Update the tooltip 369 | self.updateBrushCyclerButton() 370 | 371 | if is_enabled: 372 | # Check if we have brushes in the list 373 | if not brush_cycler.brush_list: 374 | # Try to add current brush if list is empty 375 | if brush_cycler.add_current_brush(): 376 | quickMessage("Enabled brush cycling with current brush") 377 | # Update tooltip again since brush list changed 378 | self.updateBrushCyclerButton() 379 | else: 380 | quickMessage("Enabled brush cycling, but no brushes in list. Add brushes with + button.") 381 | else: 382 | quickMessage(f"Enabled brush cycling with {len(brush_cycler.brush_list)} brushes") 383 | else: 384 | quickMessage("Disabled brush cycling") 385 | 386 | def addCurrentBrushToCycleList(self): 387 | """Add the current brush to the cycle list""" 388 | if brush_cycler.add_current_brush(): 389 | quickMessage(f"Added current brush to cycle list. Total: {len(brush_cycler.brush_list)}") 390 | # Update the tooltip to reflect the new brush count 391 | self.updateBrushCyclerButton() 392 | else: 393 | quickMessage("Could not add current brush to cycle list") 394 | 395 | def showBrushListEditor(self): 396 | """Show the brush list editor dialog""" 397 | dialog = BrushListDialog(Krita.instance().activeWindow().qwindow()) 398 | dialog.brush_list_widget.brushListChanged.connect(self.updateBrushCyclerButton) 399 | dialog.exec_() 400 | 401 | def updateBrushCyclerButton(self): 402 | """Update the brush cycler button state based on the current brush list""" 403 | # Update the button state based on whether cycling is enabled and brushes exist 404 | g.g_btn_brush_cycler.setChecked(brush_cycler.enabled) 405 | 406 | # Update the tooltip to show the number of brushes 407 | if brush_cycler.brush_list: 408 | g.g_btn_brush_cycler.setToolTip(f"Cycle brushes (enabled: {brush_cycler.enabled}, {len(brush_cycler.brush_list)} brushes)") 409 | else: 410 | g.g_btn_brush_cycler.setToolTip("Cycle brushes (no brushes in list)") 411 | 412 | def canvasChanged(self, canvas): 413 | # self.label.setText("Hellodocker: canvas changed"); 414 | pass 415 | -------------------------------------------------------------------------------- /pykrita/recent_color/mergeAllPythonFiles.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | 4 | def combine_py_files(): 5 | # Ottieni il nome dello script corrente 6 | current_script = os.path.basename(__file__) 7 | output_filename = "combined_files.py" 8 | 9 | # Trova tutti i file .py nella cartella corrente 10 | py_files = glob.glob("*.py") 11 | 12 | # Filtra escludendo lo script corrente e il file di output 13 | selected_files = [ 14 | f for f in py_files 15 | if os.path.basename(f) not in [current_script, output_filename] 16 | ] 17 | 18 | with open(output_filename, 'w', encoding='utf-8') as outfile: 19 | for i, file_path in enumerate(selected_files): 20 | filename = os.path.basename(file_path) 21 | 22 | # Crea il separatore 23 | if i == 0: 24 | separator = f"### START OF FILE: {filename} ###\n\n" 25 | else: 26 | separator = f"\n\n### START OF FILE: {filename} ###\n\n" 27 | 28 | outfile.write(separator) 29 | 30 | # Copia il contenuto del file 31 | with open(file_path, 'r', encoding='utf-8') as infile: 32 | outfile.write(infile.read()) 33 | 34 | outfile.write("\n") # Newline finale 35 | 36 | print(f"Uniti {len(selected_files)} file in {output_filename}") 37 | 38 | if __name__ == "__main__": 39 | combine_py_files() -------------------------------------------------------------------------------- /pykrita/recent_color/mouseMonitor.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Optional 2 | 3 | # mousemonitor.py 4 | from PyQt5.QtCore import QObject, Qt, QTimer, pyqtSignal 5 | from PyQt5.QtGui import QCursor 6 | from PyQt5.QtWidgets import QApplication, QWidget, QPushButton 7 | 8 | from . import globals as g # Import globals for logging 9 | 10 | from .recent_color import log 11 | 12 | # Helper function to get widget hierarchy 13 | def get_widget_hierarchy(widget: Optional[QWidget]) -> List[QWidget]: 14 | hierarchy: List[str] = [] 15 | current: Optional[QWidget] = widget 16 | while current: 17 | # class_name: str = current.metaObject().className() 18 | # object_name: str = current.objectName() 19 | # hierarchy.append(f"{class_name}({object_name or 'NoObjectName'})") # Combine class and object name 20 | hierarchy.append(current) 21 | current = current.parent() 22 | return hierarchy 23 | 24 | class MouseMonitor(QObject): 25 | mouseClicked = pyqtSignal(list) 26 | mouseReleased = pyqtSignal(list) 27 | 28 | # Attributes with type hints 29 | timer: QTimer 30 | left_button_pressed: bool 31 | last_widget: Optional[QWidget] 32 | 33 | def __init__(self) -> None: 34 | super().__init__() 35 | self.timer = QTimer() 36 | self.timer.timeout.connect(self.check_mouse) 37 | self.timer.start(50) # 50ms intervallo di controllo 38 | self.left_button_pressed = False 39 | self.last_widget = None 40 | 41 | def is_button_widget(self, widget: Optional[QWidget]) -> bool: 42 | """Verifica se il widget è un pulsante di qualsiasi tipo""" 43 | if not widget: 44 | return False 45 | 46 | # Verifica se è un QPushButton 47 | if isinstance(widget, QPushButton): 48 | return True 49 | 50 | # Verifica se il nome della classe contiene 'button' (case insensitive) 51 | class_name: str = widget.metaObject().className().lower() 52 | if 'button' in class_name: 53 | return True 54 | 55 | return False 56 | 57 | 58 | 59 | def check_mouse(self) -> None: 60 | current_buttons: Qt.MouseButtons = QApplication.mouseButtons() # Type alias for int flags 61 | current_widget: Optional[QWidget] = QApplication.widgetAt(QCursor.pos()) 62 | 63 | # Gestione pressione del tasto sinistro 64 | if current_buttons & Qt.LeftButton: 65 | if not self.left_button_pressed: 66 | self.left_button_pressed = True 67 | if current_widget: 68 | # Print widget hierarchy on mouse down 69 | hierarchy: List[str] = get_widget_hierarchy(current_widget) 70 | #log(f"Mouse down on widget hierarchy: {hierarchy}") 71 | self.mouseClicked.emit(hierarchy) 72 | self.last_widget = current_widget 73 | else: 74 | if self.left_button_pressed: 75 | self.left_button_pressed = False 76 | try: 77 | # Emettiamo il segnale solo se abbiamo un widget valido e NON è un pulsante 78 | if self.last_widget and not self.is_button_widget(self.last_widget): 79 | hierarchy = get_widget_hierarchy(self.last_widget) # Use self.last_widget 80 | self.mouseReleased.emit(hierarchy) # Emit the hierarchy list 81 | except RuntimeError: 82 | # Potentially log error or handle differently 83 | self.last_widget = None # Ensure reset even on error 84 | finally: 85 | # Always reset last_widget after release logic 86 | self.last_widget = None 87 | 88 | 89 | def isColorSelector(self, widget: Optional[QWidget]) -> bool: 90 | if not widget: 91 | return False 92 | return widget.metaObject().className() == 'KisColorSelector' 93 | 94 | def isPalette(self, hier: list[QWidget]) -> bool: 95 | if len(hier) < 2: 96 | return False 97 | else: 98 | primo = hier[0] 99 | secondo = hier[1] 100 | return ( primo.metaObject().className() == "QWidget" and primo.objectName() == "qt_scrollarea_viewport" 101 | and secondo.metaObject().className() == "KisPaletteView" ) 102 | 103 | # ['QWidget(qt_scrollarea_viewport)', 'KisPaletteView(paletteView)', ... 104 | # Note: This currently checks for KisColorSelector, not a palette view. 105 | # if not widget: 106 | # return False 107 | # return widget.metaObject().className() == 'KisColorSelector' 108 | 109 | 110 | def is_krita_canvas(self, widget: Optional[QWidget]) -> bool: 111 | """Verifica se il widget è il canvas di Krita""" 112 | 113 | #print("\niskritac1\n"); 114 | if not widget: 115 | #print(f"\n return false. widget = {widget}\n"); 116 | return False 117 | # TODO: Consider alternative ways to identify canvas if OpenGL is not used 118 | if widget.metaObject().className() == 'KisOpenGLCanvas2': 119 | #print("\ndetected canvas krita\n"); 120 | return True 121 | # parent = widget.parent() 122 | # while parent: 123 | # if hasattr(parent, 'canvas') and 'KisCanvas' in str(type(parent)): 124 | # return True 125 | # parent = parent.parent() 126 | 127 | # else: 128 | # print(f"class name { widget.metaObject().className()}. widget = {widget}\n") 129 | return False 130 | 131 | 132 | def mouseIsCurrentlyDown() -> bool: 133 | current_buttons: Qt.MouseButtons = QApplication.mouseButtons() # Type alias for int flags 134 | return current_buttons & Qt.LeftButton -------------------------------------------------------------------------------- /pykrita/recent_color/rgb.py: -------------------------------------------------------------------------------- 1 | from . globals import log 2 | 3 | from typing import Optional 4 | import math 5 | 6 | class rgb: 7 | def __init__(self, r, g, b, a): 8 | if not isinstance(r, float) or not isinstance(g, float) or not isinstance(b, float) or not isinstance(a, float): 9 | raise TypeError("r, g, and b and a must be float values (doubles)") 10 | self.a = a 11 | self.r = r 12 | self.g = g 13 | self.b = b 14 | 15 | def log(self, msg): 16 | log(f"{msg}: {self.toString()}") 17 | 18 | def toString(self): 19 | # inverto r e b perche' in realta' siamo bgr 20 | return f" b:{self.r}, g:{self.g}, r:{self.b} ,a:{self.a}" 21 | 22 | def average(self, c): 23 | return rgb((self.r + c.r) / 2.0, 24 | (self.g + c.g) / 2.0, 25 | (self.b + c.b) / 2.0, 26 | 255.0) 27 | 28 | def distance(self, c): 29 | return math.sqrt((self.r - c.r)*(self.r - c.r) + (self.g - c.g)*(self.g - c.g) + (self.b - c.b)*(self.b - c.b) ) 30 | 31 | def equals(self, c): 32 | return c.r == self.r and c.g == self.g and c.b == self.b 33 | 34 | def clone(self): 35 | return rgb(self.r, self.g, self.b, self.a) 36 | 37 | def rgbOfColorArray01(comp): 38 | """Converts a Krita color component sequence (RGBA, 0.0-1.0) to an rgb object (RGBA, 0.0-255.0).""" 39 | if not isinstance(comp, (list, tuple)) or len(comp) < 3: # Check for at least RGB 40 | raise ValueError("Input must be a sequence (list or tuple) with at least 3 components (R, G, B)") 41 | 42 | # Handle potential alpha component (use 255.0 if not present) 43 | alpha = comp[3] * 255.0 if len(comp) >= 4 else 255.0 44 | 45 | return rgb(comp[0] * 255.0, comp[1] * 255.0, comp[2] * 255.0, alpha) 46 | 47 | 48 | def rgbOfColorArray255(comp : list[float| int]) -> rgb: 49 | """Converts a Krita color component sequence (RGBA, 0.0-1.0) to an rgb object (RGBA, 0.0-255.0).""" 50 | if not isinstance(comp, (list, tuple)) or len(comp) < 3: # Check for at least RGB 51 | raise ValueError("Input must be a sequence (list or tuple) with at least 3 components (R, G, B)") 52 | 53 | # Handle potential alpha component (use 255.0 if not present) 54 | alpha = float(comp[3]) if len(comp) >= 4 else 255.0 55 | 56 | return rgb(float(comp[0]) , float( comp[1] ), float(comp[2] ), alpha) 57 | 58 | from typing import List # Add List import for type hinting 59 | 60 | def colorArrayOfRgb(rgb_color: rgb) -> List[float]: 61 | """Converts an rgb object (RGBA, 0.0-255.0) to a Krita color component array (RGBA, 0.0-1.0), with alpha forced to 1.0.""" 62 | if not isinstance(rgb_color, rgb): 63 | raise TypeError("Input must be an rgb object") 64 | 65 | # Convert R, G, B from 0-255 range to 0.0-1.0 range 66 | # Clamp values to ensure they are within [0.0, 1.0] after division 67 | r_comp = max(0.0, min(1.0, rgb_color.r / 255.0)) 68 | g_comp = max(0.0, min(1.0, rgb_color.g / 255.0)) 69 | b_comp = max(0.0, min(1.0, rgb_color.b / 255.0)) 70 | 71 | # Return as a list [R, G, B, A] with alpha = 1.0 72 | return [r_comp, g_comp, b_comp, 1.0] 73 | 74 | def colorArray01_3_OfRgb(rgb_color: rgb) -> List[float]: 75 | """Converts an rgb object (RGBA, 0.0-255.0) to a Krita color component array (RGBA, 0.0-1.0), with alpha forced to 1.0.""" 76 | if not isinstance(rgb_color, rgb): 77 | raise TypeError("Input must be an rgb object") 78 | 79 | # Convert R, G, B from 0-255 range to 0.0-1.0 range 80 | # Clamp values to ensure they are within [0.0, 1.0] after division 81 | r_comp = max(0.0, min(1.0, rgb_color.r / 255.0)) 82 | g_comp = max(0.0, min(1.0, rgb_color.g / 255.0)) 83 | b_comp = max(0.0, min(1.0, rgb_color.b / 255.0)) 84 | 85 | # Return as a list [R, G, B, A] with alpha = 1.0 86 | return [r_comp, g_comp, b_comp] 87 | 88 | def colorArray255_3_OfRgb(rgb_color: rgb) -> List[float]: 89 | """Converts an rgb object (RGBA, 0.0-255.0) to a Krita color component array (RGBA, 0.0-1.0), with alpha forced to 1.0.""" 90 | if not isinstance(rgb_color, rgb): 91 | raise TypeError(f"Input must be an rgb object but it is {rgb_color}") 92 | 93 | # Convert R, G, B from 0-255 range to 0.0-1.0 range 94 | # Clamp values to ensure they are within [0.0, 1.0] after division 95 | r_comp = rgb_color.r 96 | g_comp = rgb_color.g 97 | b_comp = rgb_color.b 98 | 99 | # Return as a list [R, G, B, A] with alpha = 1.0 100 | return [r_comp, g_comp, b_comp] 101 | 102 | 103 | def rgbOfManagedColor(c) -> Optional[rgb]: 104 | co = c.components() 105 | if len(co ) < 3: 106 | return None # sei su un layer grayscale 107 | else: 108 | return rgb(float(co[0] * 255.0), float(co[1] * 255.0), float(co[2] * 255.0), 255.0) 109 | -------------------------------------------------------------------------------- /pykrita/recent_color/slider.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QWidget, QApplication 2 | from PyQt5.QtCore import Qt, pyqtSignal, QRect 3 | from PyQt5.QtGui import QPainter, QColor, QPen, QFont, QPainterPath, QLinearGradient 4 | 5 | class KritaStyleSlider(QWidget): 6 | """ 7 | Widget che replica lo slider di Krita. 8 | - Mostra un riquadro grigio chiaro con una barra blu che indica il valore 9 | - Permette di cliccare in qualsiasi punto per impostare il valore 10 | - Mostra un testo personalizzabile e la percentuale con ombra rinforzata 11 | - Supporta la regolazione fine con la rotella del mouse 12 | - Implementa stato disabled con barra grigia e nessuna interazione 13 | - Testo semi-trasparente quando disabilitato 14 | """ 15 | valueChanged = pyqtSignal(float) 16 | 17 | def __init__(self, parent=None, label="Value"): 18 | super().__init__(parent) 19 | self._value = 0.5 # Valore iniziale tra 0.0 e 1.0 20 | self._label = label 21 | self._dragging = False 22 | 23 | # Colori 24 | self._background_color = QColor(220, 220, 220) # Grigio chiaro 25 | self._bar_color = QColor(30, 130, 255) # Blu 26 | self._bar_disabled_color = QColor(150, 150, 150) # Grigio per stato disabled 27 | self._text_color = QColor(255, 255, 255) # Bianco 28 | self._shadow_color = QColor(0, 0, 0, 180) # Nero più opaco 29 | 30 | # Impostazioni del widget 31 | self.setMinimumSize(200, 30) 32 | self.setFocusPolicy(Qt.StrongFocus) 33 | self.setCursor(Qt.PointingHandCursor) 34 | 35 | def setValue(self, value): 36 | """Imposta il valore dello slider (tra 0.0 e 1.0)""" 37 | # Limita il valore tra 0.0 e 1.0 38 | value = max(0.0, min(1.0, value)) 39 | 40 | if value != self._value: 41 | self._value = value 42 | self.valueChanged.emit(self._value) 43 | self.update() 44 | 45 | def value(self): 46 | """Ritorna il valore corrente dello slider""" 47 | return self._value 48 | 49 | def setLabel(self, label): 50 | """Imposta il testo personalizzato""" 51 | self._label = label 52 | self.update() 53 | 54 | def setEnabled(self, enabled): 55 | """Override del metodo setEnabled per aggiornare il cursore""" 56 | super().setEnabled(enabled) 57 | if enabled: 58 | self.setCursor(Qt.PointingHandCursor) 59 | else: 60 | self.setCursor(Qt.ArrowCursor) 61 | self.update() 62 | 63 | def paintEvent(self, event): 64 | """Disegna lo slider""" 65 | painter = QPainter(self) 66 | painter.setRenderHint(QPainter.Antialiasing) 67 | 68 | rect = self.rect().adjusted(2, 2, -2, -2) # Margine 69 | 70 | # Disegna il background grigio 71 | painter.fillRect(rect, self._background_color) 72 | 73 | # Calcola la larghezza della barra in base al valore 74 | bar_width = int(rect.width() * self._value) 75 | bar_rect = QRect(rect.x(), rect.y(), bar_width, rect.height()) 76 | 77 | # Disegna la barra con colore appropriato per lo stato 78 | if self.isEnabled(): 79 | bar_color = self._bar_color # Blu se abilitato 80 | else: 81 | bar_color = self._bar_disabled_color # Grigio se disabilitato 82 | 83 | painter.fillRect(bar_rect, bar_color) 84 | 85 | # Prepara il testo 86 | text = f"{self._label}: {int(self._value * 100)}%" 87 | font = painter.font() 88 | font.setBold(True) 89 | painter.setFont(font) 90 | 91 | # Imposta l'opacità del testo in base allo stato 92 | if self.isEnabled(): 93 | text_color = self._text_color 94 | shadow_color = self._shadow_color 95 | else: 96 | # Riduce l'opacità al 50% se disabilitato 97 | text_color = QColor(self._text_color) 98 | text_color.setAlpha(int(self._text_color.alpha() * 0.5)) 99 | 100 | shadow_color = QColor(self._shadow_color) 101 | shadow_color.setAlpha(int(self._shadow_color.alpha() * 0.5)) 102 | 103 | # Tecnica di ombra potenziata: disegna l'ombra più volte con offset leggermente diversi 104 | shadow_offsets = [ 105 | (1, 1), (1, 0), (0, 1), (-1, 1), (1, -1), 106 | (2, 2), (2, 1), (1, 2), (2, 0), (0, 2) 107 | ] 108 | 109 | painter.setPen(shadow_color) 110 | for offset_x, offset_y in shadow_offsets: 111 | # Usa la correzione fornita dall'utente 112 | shadow_rect = rect.adjusted(offset_x - 1, offset_y - 1, offset_x - 1, offset_y - 1) 113 | painter.drawText(shadow_rect, Qt.AlignCenter, text) 114 | 115 | # Disegna il testo principale 116 | painter.setPen(text_color) 117 | painter.drawText(rect, Qt.AlignCenter, text) 118 | 119 | def mousePressEvent(self, event): 120 | """Gestisce il click del mouse solo se il widget è abilitato""" 121 | if not self.isEnabled(): 122 | return 123 | 124 | if event.button() == Qt.LeftButton: 125 | self._dragging = True 126 | self._updateValueFromPosition(event.pos()) 127 | 128 | def mouseMoveEvent(self, event): 129 | """Gestisce il trascinamento del mouse solo se il widget è abilitato""" 130 | if not self.isEnabled(): 131 | return 132 | 133 | if self._dragging: 134 | self._updateValueFromPosition(event.pos()) 135 | 136 | def mouseReleaseEvent(self, event): 137 | """Gestisce il rilascio del mouse solo se il widget è abilitato""" 138 | if not self.isEnabled(): 139 | return 140 | 141 | if event.button() == Qt.LeftButton and self._dragging: 142 | self._dragging = False 143 | self._updateValueFromPosition(event.pos()) 144 | 145 | def wheelEvent(self, event): 146 | """Gestisce la rotella del mouse solo se il widget è abilitato""" 147 | if not self.isEnabled(): 148 | return 149 | 150 | delta = event.angleDelta().y() 151 | # Regolazione fine: cambia di 0.01 per ogni step della rotella 152 | step = 0.01 if delta > 0 else -0.01 153 | self.setValue(self._value + step) 154 | 155 | def _updateValueFromPosition(self, pos): 156 | """Aggiorna il valore in base alla posizione del mouse""" 157 | rect = self.rect().adjusted(2, 2, -2, -2) 158 | if rect.width() > 0: 159 | value = (pos.x() - rect.x()) / rect.width() 160 | value = max(0.0, min(1.0, value)) 161 | self.setValue(value) 162 | 163 | # # Esempio di utilizzo 164 | # if __name__ == "__main__": 165 | # import sys 166 | # from PyQt5.QtWidgets import QVBoxLayout, QCheckBox 167 | 168 | # app = QApplication(sys.argv) 169 | 170 | # window = QWidget() 171 | # window.setWindowTitle("Krita Style Slider Demo") 172 | # window.setGeometry(100, 100, 400, 250) 173 | 174 | # layout = QVBoxLayout() 175 | 176 | # slider1 = KritaStyleSlider(None, "Opacity") 177 | # slider1.setValue(0.7) # Imposta un valor -------------------------------------------------------------------------------- /pykrita/recent_color/spectral.py: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | ## 3 | ## Copyright (c) 2023 Ronald van Wijnen 4 | ## 5 | ## Permission is hereby granted, free of charge, to any person obtaining a 6 | ## copy of this software and associated documentation files (the "Software"), 7 | ## to deal in the Software without restriction, including without limitation 8 | ## the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | ## and/or sell copies of the Software, and to permit persons to whom the 10 | ## Software is furnished to do so, subject to the following conditions: 11 | ## 12 | ## The above copyright notice and this permission notice shall be included in 13 | ## all copies or substantial portions of the Software. 14 | ## 15 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | ## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | ## DEALINGS IN THE SOFTWARE. 22 | 23 | SIZE = 38 24 | GAMMA = 2.4 25 | EPSILON = 0.00000001 26 | 27 | SPD_C = [0.96853629, 0.96855103, 0.96859338, 0.96877345, 0.96942204, 0.97143709, 0.97541862, 0.98074186, 0.98580992, 0.98971194, 0.99238027, 0.99409844, 0.995172, 0.99576545, 0.99593552, 0.99564041, 0.99464769, 0.99229579, 0.98638762, 0.96829712, 0.89228016, 0.53740239, 0.15360445, 0.05705719, 0.03126539, 0.02205445, 0.01802271, 0.0161346, 0.01520947, 0.01475977, 0.01454263, 0.01444459, 0.01439897, 0.0143762, 0.01436343, 0.01435687, 0.0143537, 0.01435408] 28 | SPD_M = [0.51567122, 0.5401552, 0.62645502, 0.75595012, 0.92826996, 0.97223624, 0.98616174, 0.98955255, 0.98676237, 0.97312575, 0.91944277, 0.32564851, 0.13820628, 0.05015143, 0.02912336, 0.02421691, 0.02660696, 0.03407586, 0.04835936, 0.0001172, 0.00008554, 0.85267882, 0.93188793, 0.94810268, 0.94200977, 0.91478045, 0.87065445, 0.78827548, 0.65738359, 0.59909403, 0.56817268, 0.54031997, 0.52110241, 0.51041094, 0.50526577, 0.5025508, 0.50126452, 0.50083021] 29 | SPD_Y = [0.02055257, 0.02059936, 0.02062723, 0.02073387, 0.02114202, 0.02233154, 0.02556857, 0.03330189, 0.05185294, 0.10087639, 0.24000413, 0.53589066, 0.79874659, 0.91186529, 0.95399623, 0.97137099, 0.97939505, 0.98345207, 0.98553736, 0.98648905, 0.98674535, 0.98657555, 0.98611877, 0.98559942, 0.98507063, 0.98460039, 0.98425301, 0.98403909, 0.98388535, 0.98376116, 0.98368246, 0.98365023, 0.98361309, 0.98357259, 0.98353856, 0.98351247, 0.98350101, 0.98350852] 30 | SPD_R = [0.03147571, 0.03146636, 0.03140624, 0.03119611, 0.03053888, 0.02856855, 0.02459485, 0.0192952, 0.01423112, 0.01033111, 0.00765876, 0.00593693, 0.00485616, 0.00426186, 0.00409039, 0.00438375, 0.00537525, 0.00772962, 0.0136612, 0.03181352, 0.10791525, 0.46249516, 0.84604333, 0.94275572, 0.96860996, 0.97783966, 0.98187757, 0.98377315, 0.98470202, 0.98515481, 0.98537114, 0.98546685, 0.98550011, 0.98551031, 0.98550741, 0.98551323, 0.98551563, 0.98551547] 31 | SPD_G = [0.49108579, 0.46944057, 0.4016578, 0.2449042, 0.0682688, 0.02732883, 0.013606, 0.01000187, 0.01284127, 0.02636635, 0.07058713, 0.70421692, 0.85473994, 0.95081565, 0.9717037, 0.97651888, 0.97429245, 0.97012917, 0.9425863, 0.99989207, 0.99989891, 0.13823139, 0.06968113, 0.05628787, 0.06111561, 0.08987709, 0.13656016, 0.22169624, 0.32176956, 0.36157329, 0.4836192, 0.46488579, 0.47440306, 0.4857699, 0.49267971, 0.49625685, 0.49807754, 0.49889859] 32 | SPD_B = [0.97901834, 0.97901649, 0.97901118, 0.97892146, 0.97858555, 0.97743705, 0.97428075, 0.96663223, 0.94822893, 0.89937713, 0.76070164, 0.4642044, 0.20123039, 0.08808402, 0.04592894, 0.02860373, 0.02060067, 0.01656701, 0.01451549, 0.01357964, 0.01331243, 0.01347661, 0.01387181, 0.01435472, 0.01479836, 0.0151525, 0.01540513, 0.01557233, 0.0156571, 0.01571025, 0.01571916, 0.01572133, 0.01572502, 0.01571717, 0.01571905, 0.01571059, 0.01569728, 0.0157002] 33 | CIE_CMF_X = [0.00006469, 0.00021941, 0.00112057, 0.00376661, 0.01188055, 0.02328644, 0.03455942, 0.03722379, 0.03241838, 0.02123321, 0.01049099, 0.00329584, 0.00050704, 0.00094867, 0.00627372, 0.01686462, 0.02868965, 0.04267481, 0.05625475, 0.0694704, 0.08305315, 0.0861261, 0.09046614, 0.08500387, 0.07090667, 0.05062889, 0.03547396, 0.02146821, 0.01251646, 0.00680458, 0.00346457, 0.00149761, 0.0007697, 0.00040737, 0.00016901, 0.00009522, 0.00004903, 0.00002] 34 | CIE_CMF_Y = [0.00000184, 0.00000621, 0.00003101, 0.00010475, 0.00035364, 0.00095147, 0.00228226, 0.00420733, 0.0066888, 0.0098884, 0.01524945, 0.02141831, 0.03342293, 0.05131001, 0.07040208, 0.08783871, 0.09424905, 0.09795667, 0.09415219, 0.08678102, 0.07885653, 0.0635267, 0.05374142, 0.04264606, 0.03161735, 0.02088521, 0.01386011, 0.00810264, 0.0046301, 0.00249138, 0.0012593, 0.00054165, 0.00027795, 0.00014711, 0.00006103, 0.00003439, 0.00001771, 0.00000722] 35 | CIE_CMF_Z = [0.00030502, 0.00103681, 0.00531314, 0.01795439, 0.05707758, 0.11365162, 0.17335873, 0.19620658, 0.18608237, 0.13995048, 0.08917453, 0.04789621, 0.02814563, 0.01613766, 0.0077591, 0.00429615, 0.00200551, 0.00086147, 0.00036904, 0.00019143, 0.00014956, 0.00009231, 0.00006813, 0.00002883, 0.00001577, 0.00000394, 0.00000158, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 36 | XYZ_RGB = [[3.24306333, -1.53837619, -0.49893282], [-0.96896309, 1.87542451, 0.04154303], [0.05568392, -0.20417438, 1.05799454]] 37 | 38 | def linear_to_concentration(l1, l2, t): 39 | t1 = l1 * (1 - t) ** 2 40 | t2 = l2 * t ** 2 41 | 42 | return t2 / (t1 + t2) 43 | 44 | def spectral_mix(color1, color2, t): 45 | lrgb1 = srgb_to_linear(color1) 46 | lrgb2 = srgb_to_linear(color2) 47 | 48 | R1 = linear_to_reflectance(lrgb1) 49 | R2 = linear_to_reflectance(lrgb2) 50 | 51 | l1 = dotproduct(R1, CIE_CMF_Y) 52 | l2 = dotproduct(R2, CIE_CMF_Y) 53 | 54 | t = linear_to_concentration(l1, l2, t) 55 | 56 | R = [0] * SIZE 57 | 58 | for i in range(SIZE): 59 | KS = (1 - t) * ((1 - R1[i]) ** 2 / (2 * R1[i])) + t * ((1 - R2[i]) ** 2 / (2 * R2[i])) 60 | KM = 1 + KS - (KS ** 2 + 2 * KS) ** 0.5 61 | 62 | R[i] = KM 63 | 64 | xyz = reflectance_to_xyz(R) 65 | rgb = xyz_to_srgb(xyz) 66 | 67 | return rgb 68 | 69 | def uncompand(x): 70 | return x / 12.92 if x < 0.04045 else ((x + 0.055) / 1.055) ** GAMMA 71 | 72 | def compand(x): 73 | return x * 12.92 if x < 0.0031308 else 1.055 * x ** (1.0 / GAMMA) - 0.055 74 | 75 | def srgb_to_linear(srgb): 76 | r = uncompand(srgb[0] / 255) 77 | g = uncompand(srgb[1] / 255) 78 | b = uncompand(srgb[2] / 255) 79 | 80 | return [r, g, b] 81 | 82 | def linear_to_srgb(lrgb): 83 | r = compand(lrgb[0]) 84 | g = compand(lrgb[1]) 85 | b = compand(lrgb[2]) 86 | 87 | return [round(clamp(r, 0, 1) * 255), round(clamp(g, 0, 1) * 255), round(clamp(b, 0, 1) * 255)] 88 | 89 | def reflectance_to_xyz(R): 90 | x = dotproduct(R, CIE_CMF_X) 91 | y = dotproduct(R, CIE_CMF_Y) 92 | z = dotproduct(R, CIE_CMF_Z) 93 | 94 | return [x, y, z] 95 | 96 | def xyz_to_srgb(xyz): 97 | r = dotproduct(XYZ_RGB[0], xyz) 98 | g = dotproduct(XYZ_RGB[1], xyz) 99 | b = dotproduct(XYZ_RGB[2], xyz) 100 | 101 | return linear_to_srgb([r, g, b]) 102 | 103 | def spectral_upsampling(lrgb): 104 | w = min(min(lrgb[0], lrgb[1]), lrgb[2]) 105 | 106 | lrgb = [lrgb[0] - w, lrgb[1] - w, lrgb[2] - w] 107 | 108 | c = min(lrgb[1], lrgb[2]) 109 | m = min(lrgb[0], lrgb[2]) 110 | y = min(lrgb[0], lrgb[1]) 111 | r = max(0, min(lrgb[0] - lrgb[2], lrgb[0] - lrgb[1])) 112 | g = max(0, min(lrgb[1] - lrgb[2], lrgb[1] - lrgb[0])) 113 | b = max(0, min(lrgb[2] - lrgb[1], lrgb[2] - lrgb[0])) 114 | 115 | return [w, c, m, y, r, g, b] 116 | 117 | def linear_to_reflectance(lrgb): 118 | weights = spectral_upsampling(lrgb) 119 | 120 | R = [0] * SIZE 121 | 122 | for i in range(SIZE): 123 | R[i] = ( 124 | max(EPSILON, 125 | weights[0] 126 | + weights[1] * SPD_C[i] 127 | + weights[2] * SPD_M[i] 128 | + weights[3] * SPD_Y[i] 129 | + weights[4] * SPD_R[i] 130 | + weights[5] * SPD_G[i] 131 | + weights[6] * SPD_B[i] 132 | ) 133 | ) 134 | 135 | return R 136 | 137 | def dotproduct(a, b): 138 | return sum(x * y for x, y in zip(a, b)) 139 | 140 | def clamp(value, min_value, max_value): 141 | return min(max(value, min_value), max_value) -------------------------------------------------------------------------------- /pykrita/recent_color/whichtool.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # PyKritaToolKit 3 | # Copyright (C) 2019-2022 - Grum999 4 | # ----------------------------------------------------------------------------- 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | # https://spdx.org/licenses/GPL-3.0-or-later.html 8 | # ----------------------------------------------------------------------------- 9 | # A Krita plugin framework 10 | # ----------------------------------------------------------------------------- 11 | 12 | # ----------------------------------------------------------------------------- 13 | # The ekrita_tools module provides extended classes and method for Krita 14 | # 15 | # Main classes from this module 16 | # 17 | # - EKritaToolsId: 18 | # Provides tools identifiers 19 | # 20 | # - EKritaToolsCategory 21 | # Provides tools categories 22 | # 23 | # - EKritaTools: 24 | # Provides methods for quick access to tools 25 | # 26 | # ----------------------------------------------------------------------------- 27 | 28 | from krita import * 29 | 30 | from PyQt5.QtWidgets import ( 31 | QWidget, 32 | QToolButton, 33 | QDockWidget 34 | ) 35 | 36 | from PyQt5.QtCore import ( 37 | QObject, 38 | QSignalMapper, 39 | pyqtSignal as Signal 40 | ) 41 | 42 | 43 | 44 | # ----------------------------------------------------------------------------- 45 | 46 | class EInvalidValue(Exception): 47 | """An invalid value has been provided""" 48 | pass 49 | 50 | 51 | class EKritaToolsId: 52 | """Tools Id 53 | 54 | Id is normally the same for QAction and QToolButton 55 | """ 56 | SVG_SELECT = 'InteractionTool' 57 | SVG_TEXT = 'SvgTextTool' 58 | SVG_PATH = 'PathTool' 59 | SVG_CALLIGRAPHY = 'KarbonCalligraphyTool' 60 | 61 | TFM_TRANSFORM = 'KisToolTransform' 62 | TFM_MOVE = 'KritaTransform/KisToolMove' 63 | TFM_CROP = 'KisToolCrop' 64 | 65 | PAINT_BRUSH = 'KritaShape/KisToolBrush' 66 | PAINT_LINE = 'KritaShape/KisToolLine' 67 | PAINT_RECTANGLE = 'KritaShape/KisToolRectangle' 68 | PAINT_ELLIPSE = 'KritaShape/KisToolEllipse' 69 | PAINT_POLYGON = 'KisToolPolygon' 70 | PAINT_POLYLINE = 'KisToolPolyline' 71 | PAINT_PATH = 'KisToolPath' 72 | PAINT_PENCIL = 'KisToolPencil' 73 | PAINT_DYNAMIC_BRUSH = 'KritaShape/KisToolDyna' 74 | PAINT_MULTI_BRUSH = 'KritaShape/KisToolMultiBrush' 75 | 76 | FILL_GRADIENT = 'KritaFill/KisToolGradient' 77 | FILL_COLORSAMPLER = 'KritaSelected/KisToolColorSampler' 78 | FILL_COLORMASK = 'KritaShape/KisToolLazyBrush' 79 | FILL_SMARTPATCH = 'KritaShape/KisToolSmartPatch' 80 | FILL_BUCKET = 'KritaFill/KisToolFill' 81 | FILL_ENCLOSEFILL = 'KisToolEncloseAndFill' 82 | 83 | HELPER_ASSISTANT = 'KisAssistantTool' 84 | HELPER_MEASURE = 'KritaShape/KisToolMeasure' 85 | HELPER_REFIMG = 'ToolReferenceImages' 86 | 87 | SELECT_RECTANGLE = 'KisToolSelectRectangular' 88 | SELECT_ELLIPSE = 'KisToolSelectElliptical' 89 | SELECT_POLYGON = 'KisToolSelectPolygonal' 90 | SELECT_OUTLINE = 'KisToolSelectOutline' 91 | SELECT_CONTIGUOUS = 'KisToolSelectContiguous' 92 | SELECT_SIMILAR = 'KisToolSelectSimilar' 93 | SELECT_PATH = 'KisToolSelectPath' 94 | SELECT_MAGNETIC = 'KisToolSelectMagnetic' 95 | 96 | VIEW_ZOOM = 'ZoomTool' 97 | VIEW_PAN = 'PanTool' 98 | 99 | 100 | class EKritaToolsCategory: 101 | """Categories to classify tools""" 102 | SVG = 'vector' 103 | TFM = 'transform' 104 | PAINT = 'paint' 105 | FILL = 'fill' 106 | HELPER = 'helper' 107 | SELECT = 'selection' 108 | VIEW = 'view' 109 | 110 | 111 | class EKritaTools: 112 | """Tools definition""" 113 | 114 | class EKritaToolNotifier(QObject): 115 | toolChanged = Signal(str, bool) 116 | 117 | __TOOLS = { 118 | EKritaToolsId.SVG_SELECT: { 119 | 'label': i18n('Select Shapes Tool'), 120 | 'category': EKritaToolsCategory.SVG, 121 | 'widget': None 122 | }, 123 | EKritaToolsId.SVG_TEXT: { 124 | 'label': i18n('Text Tool'), 125 | 'category': EKritaToolsCategory.SVG, 126 | 'widget': None 127 | }, 128 | EKritaToolsId.SVG_PATH: { 129 | 'label': i18n('Edit Shapes Tool'), 130 | 'category': EKritaToolsCategory.SVG, 131 | 'widget': None 132 | }, 133 | EKritaToolsId.SVG_CALLIGRAPHY: { 134 | 'label': i18n('Calligraphy'), 135 | 'category': EKritaToolsCategory.SVG, 136 | 'widget': None 137 | }, 138 | 139 | EKritaToolsId.TFM_TRANSFORM: { 140 | 'label': i18n('Transform a layer or a selection'), 141 | 'category': EKritaToolsCategory.TFM, 142 | 'widget': None 143 | }, 144 | EKritaToolsId.TFM_MOVE: { 145 | 'label': i18n('Move a layer'), 146 | 'category': EKritaToolsCategory.TFM, 147 | 'widget': None 148 | }, 149 | EKritaToolsId.TFM_CROP: { 150 | 'label': i18n('Crop the image to an area'), 151 | 'category': EKritaToolsCategory.TFM, 152 | 'widget': None 153 | }, 154 | 155 | EKritaToolsId.PAINT_BRUSH: { 156 | 'label': i18n("Freehand Brush Tool"), 157 | 'category': EKritaToolsCategory.PAINT, 158 | 'widget': None 159 | }, 160 | EKritaToolsId.PAINT_LINE: { 161 | 'label': i18n("Line Tool"), 162 | 'category': EKritaToolsCategory.PAINT, 163 | 'widget': None 164 | }, 165 | EKritaToolsId.PAINT_RECTANGLE: { 166 | 'label': i18n("Rectangle Tool"), 167 | 'category': EKritaToolsCategory.PAINT, 168 | 'widget': None 169 | }, 170 | EKritaToolsId.PAINT_ELLIPSE: { 171 | 'label': i18n("Ellipse Tool"), 172 | 'category': EKritaToolsCategory.PAINT, 173 | 'widget': None 174 | }, 175 | EKritaToolsId.PAINT_POLYGON: { 176 | 'label': i18n("Polygon Tool: Shift-mouseclick ends the polygon."), 177 | 'category': EKritaToolsCategory.PAINT, 178 | 'widget': None 179 | }, 180 | EKritaToolsId.PAINT_POLYLINE: { 181 | 'label': i18n("Polyline Tool: Shift-mouseclick ends the polyline."), 182 | 'category': EKritaToolsCategory.PAINT, 183 | 'widget': None 184 | }, 185 | EKritaToolsId.PAINT_PATH: { 186 | 'label': i18n("Bezier Curve Tool: Shift-mouseclick ends the curve."), 187 | 'category': EKritaToolsCategory.PAINT, 188 | 'widget': None 189 | }, 190 | EKritaToolsId.PAINT_PENCIL: { 191 | 'label': i18n("Freehand Path Tool"), 192 | 'category': EKritaToolsCategory.PAINT, 193 | 'widget': None 194 | }, 195 | EKritaToolsId.PAINT_DYNAMIC_BRUSH: { 196 | 'label': i18n("Dynamic Brush Tool"), 197 | 'category': EKritaToolsCategory.PAINT, 198 | 'widget': None 199 | }, 200 | EKritaToolsId.PAINT_MULTI_BRUSH: { 201 | 'label': i18n("Multibrush Tool"), 202 | 'category': EKritaToolsCategory.PAINT, 203 | 'widget': None 204 | }, 205 | 206 | EKritaToolsId.FILL_GRADIENT: { 207 | 'label': i18n('Draw a gradient.'), 208 | 'category': EKritaToolsCategory.FILL, 209 | 'widget': None 210 | }, 211 | EKritaToolsId.FILL_COLORSAMPLER: { 212 | 'label': i18n('Sample a colour from the image or current layer'), 213 | 'category': EKritaToolsCategory.FILL, 214 | 'widget': None 215 | }, 216 | EKritaToolsId.FILL_COLORMASK: { 217 | 'label': i18n('Colourise Mask Tool'), 218 | 'category': EKritaToolsCategory.FILL, 219 | 'widget': None 220 | }, 221 | EKritaToolsId.FILL_SMARTPATCH: { 222 | 'label': i18n('Smart Patch Tool'), 223 | 'category': EKritaToolsCategory.FILL, 224 | 'widget': None 225 | }, 226 | EKritaToolsId.FILL_BUCKET: { 227 | 'label': i18n('Fill a contiguous area of colour with a colour, or fill a selection.'), 228 | 'category': EKritaToolsCategory.FILL, 229 | 'widget': None 230 | }, 231 | EKritaToolsId.FILL_ENCLOSEFILL: { 232 | 'label': i18n('Enclose and Fill Tool'), 233 | 'category': EKritaToolsCategory.FILL, 234 | 'widget': None 235 | }, 236 | 237 | EKritaToolsId.HELPER_ASSISTANT: { 238 | 'label': i18n('Assistant Tool'), 239 | 'category': EKritaToolsCategory.HELPER, 240 | 'widget': None 241 | }, 242 | EKritaToolsId.HELPER_MEASURE: { 243 | 'label': i18n('Measure the distance between two points'), 244 | 'category': EKritaToolsCategory.HELPER, 245 | 'widget': None 246 | }, 247 | EKritaToolsId.HELPER_REFIMG: { 248 | 'label': i18n('Reference Images Tool'), 249 | 'category': EKritaToolsCategory.HELPER, 250 | 'widget': None 251 | }, 252 | 253 | EKritaToolsId.SELECT_RECTANGLE: { 254 | 'label': i18n('Rectangular Selection Tool'), 255 | 'category': EKritaToolsCategory.SELECT, 256 | 'widget': None 257 | }, 258 | EKritaToolsId.SELECT_ELLIPSE: { 259 | 'label': i18n('Elliptical Selection Tool'), 260 | 'category': EKritaToolsCategory.SELECT, 261 | 'widget': None 262 | }, 263 | EKritaToolsId.SELECT_POLYGON: { 264 | 'label': i18n('Polygonal Selection Tool'), 265 | 'category': EKritaToolsCategory.SELECT, 266 | 'widget': None 267 | }, 268 | EKritaToolsId.SELECT_OUTLINE: { 269 | 'label': i18n('Freehand Selection Tool'), 270 | 'category': EKritaToolsCategory.SELECT, 271 | 'widget': None 272 | }, 273 | EKritaToolsId.SELECT_CONTIGUOUS: { 274 | 'label': i18n('Contiguous Selection Tool'), 275 | 'category': EKritaToolsCategory.SELECT, 276 | 'widget': None 277 | }, 278 | EKritaToolsId.SELECT_SIMILAR: { 279 | 'label': i18n('Similar Colour Selection Tool'), 280 | 'category': EKritaToolsCategory.SELECT, 281 | 'widget': None 282 | }, 283 | EKritaToolsId.SELECT_PATH: { 284 | 'label': i18n('Bezier Curve Selection Tool'), 285 | 'category': EKritaToolsCategory.SELECT, 286 | 'widget': None 287 | }, 288 | EKritaToolsId.SELECT_MAGNETIC: { 289 | 'label': i18n('Magnetic Curve Selection Tool'), 290 | 'category': EKritaToolsCategory.SELECT, 291 | 'widget': None 292 | }, 293 | 294 | EKritaToolsId.VIEW_ZOOM: { 295 | 'label': i18n('Zoom Tool'), 296 | 'category': EKritaToolsCategory.VIEW, 297 | 'widget': None 298 | }, 299 | EKritaToolsId.VIEW_PAN: { 300 | 'label': i18n('Pan Tool'), 301 | 'category': EKritaToolsCategory.VIEW, 302 | 'widget': None 303 | } 304 | } 305 | 306 | __notifier = None 307 | __toolbox = None 308 | __signalMapper = QSignalMapper() 309 | 310 | notifier = EKritaToolNotifier() 311 | 312 | @staticmethod 313 | def init(): 314 | """Initialise static class""" 315 | def __toolChanged(id): 316 | """tool has been changed, emit signal""" 317 | EKritaTools.notifier.toolChanged.emit(id, EKritaTools.__TOOLS[id]['widget'].isChecked()) 318 | 319 | def __windowCreated(): 320 | """Executed when a Krita window is created""" 321 | if EKritaTools.__toolbox is None: 322 | 323 | EKritaTools.__toolbox = Krita.instance().activeWindow().qwindow().findChild(QDockWidget, 'ToolBox') 324 | 325 | EKritaTools.__signalMapper.mapped[str].connect(__toolChanged) 326 | 327 | for id in list(EKritaTools.__TOOLS.keys()): 328 | toolButton = EKritaTools.__toolbox.findChild(QToolButton, id) 329 | if toolButton: 330 | EKritaTools.__TOOLS[id]['widget'] = toolButton 331 | toolButton.toggled.connect(EKritaTools.__signalMapper.map) 332 | EKritaTools.__signalMapper.setMapping(toolButton, id) 333 | 334 | if EKritaTools.__toolbox is None: 335 | EKritaTools.toolChanged = Signal(str, bool) 336 | EKritaTools.__notifier = Krita.instance().notifier() 337 | 338 | EKritaTools.__notifier.setActive(True) 339 | EKritaTools.__notifier.windowCreated.connect(__windowCreated) 340 | 341 | @staticmethod 342 | def list(filter=None): 343 | """Return list of available tools Id 344 | 345 | If given, `filter` is EKritaToolsCategory value or a list of EKritaToolsCategory values 346 | In this case, only tools matching given category are returned 347 | 348 | if given `filter` is not valid, return empty list 349 | """ 350 | if filter is None: 351 | return list(EKritaTools.__TOOLS.keys()) 352 | else: 353 | if isinstance(filter, str): 354 | # convert to list 355 | filter = [filter] 356 | if isinstance(filter, (list, tuple)): 357 | return [id for id in list(EKritaTools.__TOOLS.keys()) if EKritaTools.__TOOLS[id]['category'] in filter] 358 | return [] 359 | 360 | @staticmethod 361 | def get(id): 362 | """Return tool definition for given id 363 | 364 | returned definition is a dictionnary with following keys: 365 | 'label': , translated label for tool 366 | 'category': , group for tool 367 | 'widget': , widget for tool 368 | """ 369 | try: 370 | return EKritaTools.__TOOLS[id] 371 | except Exception: 372 | raise EInvalidValue("Given `id` is not valid") 373 | 374 | @staticmethod 375 | def name(id): 376 | """Return tool label definition for given id""" 377 | try: 378 | return EKritaTools.__TOOLS[id]['label'] 379 | except Exception: 380 | raise EInvalidValue("Given `id` is not valid") 381 | 382 | @staticmethod 383 | def category(id): 384 | """Return tool category definition for given id""" 385 | try: 386 | return EKritaTools.__TOOLS[id]['category'] 387 | except Exception: 388 | raise EInvalidValue("Given `id` is not valid") 389 | 390 | @staticmethod 391 | def current(filter=None): 392 | """return id of current tool 393 | 394 | If given, `filter` is EKritaToolsCategory value or a list of EKritaToolsCategory values 395 | In this case, id is returned for tools for which category is matching given filter; Otherwise return None 396 | 397 | if given `filter` is not valid, return None 398 | """ 399 | for id in EKritaTools.list(filter): 400 | toolButton = EKritaTools.__TOOLS[id]['widget'] 401 | if toolButton and toolButton.isChecked(): 402 | return id 403 | return None 404 | 405 | @staticmethod 406 | def setCurrent(id): 407 | """Set current paint tool from given `id`""" 408 | if id in EKritaTools.__TOOLS and id != EKritaTools.current(): 409 | Krita.instance().action(id).trigger() 410 | 411 | 412 | # ------------------------------------------------------------------------------------- 413 | # initialise module 414 | EKritaTools.init() --------------------------------------------------------------------------------