diff --git a/3rdparty/ext_vc/0001-Workaround-AVX-argument-failures.patch b/3rdparty/ext_vc/0001-Workaround-AVX-argument-failures.patch new file mode 100644 index 0000000000..69feb76056 --- /dev/null +++ b/3rdparty/ext_vc/0001-Workaround-AVX-argument-failures.patch @@ -0,0 +1,36 @@ +From 9cba6ee74397e2b3a6f1f225e2e716bf903bc7f0 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 10 Apr 2019 11:03:29 +0300 +Subject: [PATCH] Workaround AVX argument failures + +Define Vector::AsArg to be a const reference. The interleave functions +already use AsArg. + +Refs: gh-241 +--- + avx/vector.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/avx/vector.h b/avx/vector.h +index d815cd45..9858ed68 100644 +--- a/avx/vector.h ++++ b/avx/vector.h +@@ -107,7 +107,15 @@ public: + SimdArray, + SimdArray>::type IndexType; + #endif ++ ++#if defined __WIN64__ && defined __GNUC__ ++ // Passing Vector by value leads to misaligned loads and stores. This works around ++ // the bug https://github.com/VcDevel/Vc/issues/241 ++ using AsArg = const Vector &; ++#else + typedef Vector AsArg; ++#endif ++ + typedef VectorType VectorTypeArg; + + protected: +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_vc/CMakeLists.txt b/3rdparty/ext_vc/CMakeLists.txt index 6cbea6db7d..cd618c5167 100644 --- a/3rdparty/ext_vc/CMakeLists.txt +++ b/3rdparty/ext_vc/CMakeLists.txt @@ -1,12 +1,44 @@ SET(PREFIX_ext_vc "${EXTPREFIX}" ) -ExternalProject_Add( ext_vc - DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz - URL_HASH SHA1=3d3ddd29eca7c2b541fd8d0f00923e57f58d5ef0 - INSTALL_DIR ${PREFIX_ext_vc} +if(APPLE) + ExternalProject_Add( ext_vc + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://github.com/VcDevel/Vc/releases/download/1.4.1/Vc-1.4.1.tar.gz + URL_HASH SHA1=46e852ab69192cf017a31d7c6d0b35d8aa8fea2d - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 + INSTALL_DIR ${PREFIX_ext_vc} - UPDATE_COMMAND "" -) + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 + + UPDATE_COMMAND "" + ) + +elseif (WIN32) + + ExternalProject_Add( ext_vc + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz + URL_HASH SHA1=3d3ddd29eca7c2b541fd8d0f00923e57f58d5ef0 + + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Workaround-AVX-argument-failures.patch + + INSTALL_DIR ${PREFIX_ext_vc} + + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 + + UPDATE_COMMAND "" + ) + +else() + ExternalProject_Add( ext_vc + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz + URL_HASH SHA1=3d3ddd29eca7c2b541fd8d0f00923e57f58d5ef0 + + INSTALL_DIR ${PREFIX_ext_vc} + + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 + + UPDATE_COMMAND "" + ) +endif() diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 01b8414ed0..6ec34e9c2a 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,93 +1,93 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ${CMAKE_SOURCE_DIR}/libs/pigment ${CMAKE_SOURCE_DIR}/libs/pigment/compositeops ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) set(LINK_VC_LIB) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}") set(LINK_VC_LIB ${Vc_LIBRARIES}) endif() macro_add_unittest_definitions() ########### next target ############### set(kis_datamanager_benchmark_SRCS kis_datamanager_benchmark.cpp) set(kis_hiterator_benchmark_SRCS kis_hline_iterator_benchmark.cpp) set(kis_viterator_benchmark_SRCS kis_vline_iterator_benchmark.cpp) set(kis_random_iterator_benchmark_SRCS kis_random_iterator_benchmark.cpp) set(kis_projection_benchmark_SRCS kis_projection_benchmark.cpp) set(kis_bcontrast_benchmark_SRCS kis_bcontrast_benchmark.cpp) set(kis_blur_benchmark_SRCS kis_blur_benchmark.cpp) set(kis_level_filter_benchmark_SRCS kis_level_filter_benchmark.cpp) set(kis_painter_benchmark_SRCS kis_painter_benchmark.cpp) set(kis_stroke_benchmark_SRCS kis_stroke_benchmark.cpp) set(kis_fast_math_benchmark_SRCS kis_fast_math_benchmark.cpp) set(kis_floodfill_benchmark_SRCS kis_floodfill_benchmark.cpp) set(kis_gradient_benchmark_SRCS kis_gradient_benchmark.cpp) set(kis_mask_generator_benchmark_SRCS kis_mask_generator_benchmark.cpp) set(kis_low_memory_benchmark_SRCS kis_low_memory_benchmark.cpp) set(KisAnimationRenderingBenchmark_SRCS KisAnimationRenderingBenchmark.cpp) set(kis_filter_selections_benchmark_SRCS kis_filter_selections_benchmark.cpp) if (UNIX) -# set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp) + set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp) endif() set(kis_thumbnail_benchmark_SRCS kis_thumbnail_benchmark.cpp) krita_add_benchmark(KisDatamanagerBenchmark TESTNAME krita-benchmarks-KisDataManager ${kis_datamanager_benchmark_SRCS}) krita_add_benchmark(KisHLineIteratorBenchmark TESTNAME krita-benchmarks-KisHLineIterator ${kis_hiterator_benchmark_SRCS}) krita_add_benchmark(KisVLineIteratorBenchmark TESTNAME krita-benchmarks-KisVLineIterator ${kis_viterator_benchmark_SRCS}) krita_add_benchmark(KisRandomIteratorBenchmark TESTNAME krita-benchmarks-KisRandomIterator ${kis_random_iterator_benchmark_SRCS}) krita_add_benchmark(KisProjectionBenchmark TESTNAME krita-benchmarks-KisProjectionBenchmark ${kis_projection_benchmark_SRCS}) krita_add_benchmark(KisBContrastBenchmark TESTNAME krita-benchmarks-KisBContrastBenchmark ${kis_bcontrast_benchmark_SRCS}) krita_add_benchmark(KisBlurBenchmark TESTNAME krita-benchmarks-KisBlurBenchmark ${kis_blur_benchmark_SRCS}) krita_add_benchmark(KisLevelFilterBenchmark TESTNAME krita-benchmarks-KisLevelFilterBenchmark ${kis_level_filter_benchmark_SRCS}) krita_add_benchmark(KisPainterBenchmark TESTNAME krita-benchmarks-KisPainterBenchmark ${kis_painter_benchmark_SRCS}) krita_add_benchmark(KisStrokeBenchmark TESTNAME krita-benchmarks-KisStrokeBenchmark ${kis_stroke_benchmark_SRCS}) krita_add_benchmark(KisFastMathBenchmark TESTNAME krita-benchmarks-KisFastMath ${kis_fast_math_benchmark_SRCS}) krita_add_benchmark(KisFloodfillBenchmark TESTNAME krita-benchmarks-KisFloodFill ${kis_floodfill_benchmark_SRCS}) krita_add_benchmark(KisGradientBenchmark TESTNAME krita-benchmarks-KisGradientFill ${kis_gradient_benchmark_SRCS}) krita_add_benchmark(KisMaskGeneratorBenchmark TESTNAME krita-benchmarks-KisMaskGenerator ${kis_mask_generator_benchmark_SRCS}) krita_add_benchmark(KisLowMemoryBenchmark TESTNAME krita-benchmarks-KisLowMemory ${kis_low_memory_benchmark_SRCS}) krita_add_benchmark(KisAnimationRenderingBenchmark TESTNAME krita-benchmarks-KisAnimationRenderingBenchmark ${KisAnimationRenderingBenchmark_SRCS}) krita_add_benchmark(KisFilterSelectionsBenchmark TESTNAME krita-image-KisFilterSelectionsBenchmark ${kis_filter_selections_benchmark_SRCS}) if(UNIX) -# krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS}) + krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS}) endif() krita_add_benchmark(KisThumbnailBenchmark TESTNAME krita-benchmarks-KisThumbnail ${kis_thumbnail_benchmark_SRCS}) target_link_libraries(KisDatamanagerBenchmark kritaimage Qt5::Test) target_link_libraries(KisHLineIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisVLineIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisRandomIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisProjectionBenchmark kritaimage kritaui Qt5::Test) target_link_libraries(KisBContrastBenchmark kritaimage Qt5::Test) target_link_libraries(KisBlurBenchmark kritaimage Qt5::Test) target_link_libraries(KisLevelFilterBenchmark kritaimage Qt5::Test) target_link_libraries(KisPainterBenchmark kritaimage Qt5::Test) target_link_libraries(KisStrokeBenchmark kritaimage Qt5::Test) target_link_libraries(KisFastMathBenchmark kritaimage Qt5::Test) target_link_libraries(KisFloodfillBenchmark kritaimage Qt5::Test) target_link_libraries(KisGradientBenchmark kritaimage Qt5::Test) target_link_libraries(KisLowMemoryBenchmark kritaimage Qt5::Test) target_link_libraries(KisAnimationRenderingBenchmark kritaimage kritaui Qt5::Test) target_link_libraries(KisFilterSelectionsBenchmark kritaimage Qt5::Test) if(UNIX) -# target_link_libraries(KisCompositionBenchmark kritaimage Qt5::Test ${LINK_VC_LIB}) + target_link_libraries(KisCompositionBenchmark kritaimage Qt5::Test ${LINK_VC_LIB}) if(HAVE_VC) -# set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}") + set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}") endif() endif() target_link_libraries(KisMaskGeneratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisThumbnailBenchmark kritaimage Qt5::Test) diff --git a/benchmarks/kis_composition_benchmark.cpp b/benchmarks/kis_composition_benchmark.cpp index 81b28defb0..a19ee5c757 100644 --- a/benchmarks/kis_composition_benchmark.cpp +++ b/benchmarks/kis_composition_benchmark.cpp @@ -1,950 +1,950 @@ /* * Copyright (c) 2012 Dmitry Kazakov * Copyright (c) 2015 Thorsten Zachmann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // for calculation of the needed alignment #include #ifdef HAVE_VC #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #include #include #include #endif #include "kis_composition_benchmark.h" #include #include #include #include #include #include #include #include "KoOptimizedCompositeOpFactory.h" // for posix_memalign() #include #include #if defined _MSC_VER #define MEMALIGN_ALLOC(p, a, s) ((*(p)) = _aligned_malloc((s), (a)), *(p) ? 0 : errno) #define MEMALIGN_FREE(p) _aligned_free((p)) #else #define MEMALIGN_ALLOC(p, a, s) posix_memalign((p), (a), (s)) #define MEMALIGN_FREE(p) free((p)) #endif const int alpha_pos = 3; enum AlphaRange { ALPHA_ZERO, ALPHA_UNIT, ALPHA_RANDOM }; template inline channel_type generateAlphaValue(AlphaRange range, RandomGenerator &rnd) { channel_type value = 0; switch (range) { case ALPHA_ZERO: break; case ALPHA_UNIT: value = rnd.unit(); break; case ALPHA_RANDOM: value = rnd(); break; } return value; } #include #include #include template struct RandomGenerator { channel_type operator() () { qFatal("Wrong template instantiation"); return channel_type(0); } channel_type unit() { qFatal("Wrong template instantiation"); return channel_type(0); } }; template <> struct RandomGenerator { RandomGenerator(int seed) : m_smallint(0,255), m_rnd(seed) { } quint8 operator() () { return m_smallint(m_rnd); } quint8 unit() { return KoColorSpaceMathsTraits::unitValue; } boost::uniform_smallint m_smallint; boost::mt11213b m_rnd; }; template <> struct RandomGenerator { RandomGenerator(int seed) : m_rnd(seed) { } float operator() () { //return float(m_rnd()) / float(m_rnd.max()); return m_smallfloat(m_rnd); } float unit() { return KoColorSpaceMathsTraits::unitValue; } boost::uniform_real m_smallfloat; boost::mt11213b m_rnd; }; template <> struct RandomGenerator : RandomGenerator { RandomGenerator(int seed) : RandomGenerator(seed) { } }; template void generateDataLine(uint seed, int numPixels, quint8 *srcPixels, quint8 *dstPixels, quint8 *mask, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange) { Q_ASSERT(numPixels >= 4); RandomGenerator rnd(seed); RandomGenerator maskRnd(seed + 1); channel_type *srcArray = reinterpret_cast(srcPixels); channel_type *dstArray = reinterpret_cast(dstPixels); for (int i = 0; i < numPixels; i++) { for (int j = 0; j < 3; j++) { channel_type s = rnd(); channel_type d = rnd(); *(srcArray++) = s; *(dstArray++) = d; } channel_type sa = generateAlphaValue(srcAlphaRange, rnd); channel_type da = generateAlphaValue(dstAlphaRange, rnd); *(srcArray++) = sa; *(dstArray++) = da; *(mask++) = maskRnd(); } } void printData(int numPixels, quint8 *srcPixels, quint8 *dstPixels, quint8 *mask) { for (int i = 0; i < numPixels; i++) { - dbgKrita << "Src: " + qDebug() << "Src: " << srcPixels[i*4] << "\t" << srcPixels[i*4+1] << "\t" << srcPixels[i*4+2] << "\t" << srcPixels[i*4+3] << "\t" << "Msk:" << mask[i]; - dbgKrita << "Dst: " + qDebug() << "Dst: " << dstPixels[i*4] << "\t" << dstPixels[i*4+1] << "\t" << dstPixels[i*4+2] << "\t" << dstPixels[i*4+3]; } } const int rowStride = 64; const int totalRows = 64; const QRect processRect(0,0,64,64); const int numPixels = rowStride * totalRows; const int numTiles = 1024; struct Tile { quint8 *src; quint8 *dst; quint8 *mask; }; #include QVector generateTiles(int size, const int srcAlignmentShift, const int dstAlignmentShift, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange, const quint32 pixelSize) { QVector tiles(size); #ifdef HAVE_VC const int vecSize = Vc::float_v::size(); #else const int vecSize = 1; #endif // the 256 are used to make sure that we have a good alignment no matter what build options are used. const size_t pixelAlignment = qMax(size_t(vecSize * sizeof(float)), size_t(256)); const size_t maskAlignment = qMax(size_t(vecSize), size_t(256)); for (int i = 0; i < size; i++) { void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, pixelAlignment, numPixels * pixelSize + srcAlignmentShift); if (error) { qFatal("posix_memalign failed: %d", error); } tiles[i].src = (quint8*)ptr + srcAlignmentShift; error = MEMALIGN_ALLOC(&ptr, pixelAlignment, numPixels * pixelSize + dstAlignmentShift); if (error) { qFatal("posix_memalign failed: %d", error); } tiles[i].dst = (quint8*)ptr + dstAlignmentShift; error = MEMALIGN_ALLOC(&ptr, maskAlignment, numPixels); if (error) { qFatal("posix_memalign failed: %d", error); } tiles[i].mask = (quint8*)ptr; if (pixelSize == 4) { generateDataLine(1, numPixels, tiles[i].src, tiles[i].dst, tiles[i].mask, srcAlphaRange, dstAlphaRange); } else if (pixelSize == 16) { generateDataLine(1, numPixels, tiles[i].src, tiles[i].dst, tiles[i].mask, srcAlphaRange, dstAlphaRange); } else { qFatal("Pixel size %i is not implemented", pixelSize); } } return tiles; } void freeTiles(QVector tiles, const int srcAlignmentShift, const int dstAlignmentShift) { Q_FOREACH (const Tile &tile, tiles) { MEMALIGN_FREE(tile.src - srcAlignmentShift); MEMALIGN_FREE(tile.dst - dstAlignmentShift); MEMALIGN_FREE(tile.mask); } } template inline bool fuzzyCompare(channel_type a, channel_type b, channel_type prec) { return qAbs(a - b) <= prec; } template inline bool comparePixels(channel_type *p1, channel_type *p2, channel_type prec) { return (p1[3] == p2[3] && p1[3] == 0) || (fuzzyCompare(p1[0], p2[0], prec) && fuzzyCompare(p1[1], p2[1], prec) && fuzzyCompare(p1[2], p2[2], prec) && fuzzyCompare(p1[3], p2[3], prec)); } template bool compareTwoOpsPixels(QVector &tiles, channel_type prec) { channel_type *dst1 = reinterpret_cast(tiles[0].dst); channel_type *dst2 = reinterpret_cast(tiles[1].dst); channel_type *src1 = reinterpret_cast(tiles[0].src); channel_type *src2 = reinterpret_cast(tiles[1].src); for (int i = 0; i < numPixels; i++) { if (!comparePixels(dst1, dst2, prec)) { - dbgKrita << "Wrong result:" << i; - dbgKrita << "Act: " << dst1[0] << dst1[1] << dst1[2] << dst1[3]; - dbgKrita << "Exp: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; - dbgKrita << "Dif: " << dst1[0] - dst2[0] << dst1[1] - dst2[1] << dst1[2] - dst2[2] << dst1[3] - dst2[3]; + qDebug() << "Wrong result:" << i; + qDebug() << "Act: " << dst1[0] << dst1[1] << dst1[2] << dst1[3]; + qDebug() << "Exp: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; + qDebug() << "Dif: " << dst1[0] - dst2[0] << dst1[1] - dst2[1] << dst1[2] - dst2[2] << dst1[3] - dst2[3]; channel_type *s1 = src1 + 4 * i; channel_type *s2 = src2 + 4 * i; - dbgKrita << "SrcA:" << s1[0] << s1[1] << s1[2] << s1[3]; - dbgKrita << "SrcE:" << s2[0] << s2[1] << s2[2] << s2[3]; + qDebug() << "SrcA:" << s1[0] << s1[1] << s1[2] << s1[3]; + qDebug() << "SrcE:" << s2[0] << s2[1] << s2[2] << s2[3]; - dbgKrita << "MskA:" << tiles[0].mask[i]; - dbgKrita << "MskE:" << tiles[1].mask[i]; + qDebug() << "MskA:" << tiles[0].mask[i]; + qDebug() << "MskE:" << tiles[1].mask[i]; return false; } dst1 += 4; dst2 += 4; } return true; } bool compareTwoOps(bool haveMask, const KoCompositeOp *op1, const KoCompositeOp *op2) { Q_ASSERT(op1->colorSpace()->pixelSize() == op2->colorSpace()->pixelSize()); const quint32 pixelSize = op1->colorSpace()->pixelSize(); const int alignment = 16; QVector tiles = generateTiles(2, alignment, alignment, ALPHA_RANDOM, ALPHA_RANDOM, op1->colorSpace()->pixelSize()); KoCompositeOp::ParameterInfo params; params.dstRowStride = 4 * rowStride; params.srcRowStride = 4 * rowStride; params.maskRowStride = rowStride; params.rows = processRect.height(); params.cols = processRect.width(); // This is a hack as in the old version we get a rounding of opacity to this value params.opacity = float(Arithmetic::scale(0.5*1.0f))/255.0; params.flow = 0.3*1.0f; params.channelFlags = QBitArray(); params.dstRowStart = tiles[0].dst; params.srcRowStart = tiles[0].src; params.maskRowStart = haveMask ? tiles[0].mask : 0; op1->composite(params); params.dstRowStart = tiles[1].dst; params.srcRowStart = tiles[1].src; params.maskRowStart = haveMask ? tiles[1].mask : 0; op2->composite(params); bool compareResult = true; if (pixelSize == 4) { compareResult = compareTwoOpsPixels(tiles, 10); } else if (pixelSize == 16) { compareResult = compareTwoOpsPixels(tiles, 2e-7); } else { qFatal("Pixel size %i is not implemented", pixelSize); } freeTiles(tiles, alignment, alignment); return compareResult; } QString getTestName(bool haveMask, const int srcAlignmentShift, const int dstAlignmentShift, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange) { QString testName; testName += !srcAlignmentShift && !dstAlignmentShift ? "Aligned " : !srcAlignmentShift && dstAlignmentShift ? "SrcUnalig " : srcAlignmentShift && !dstAlignmentShift ? "DstUnalig " : srcAlignmentShift && dstAlignmentShift ? "Unaligned " : "###"; testName += haveMask ? "Mask " : "NoMask "; testName += srcAlphaRange == ALPHA_RANDOM ? "SrcRand " : srcAlphaRange == ALPHA_ZERO ? "SrcZero " : srcAlphaRange == ALPHA_UNIT ? "SrcUnit " : "###"; testName += dstAlphaRange == ALPHA_RANDOM ? "DstRand" : dstAlphaRange == ALPHA_ZERO ? "DstZero" : dstAlphaRange == ALPHA_UNIT ? "DstUnit" : "###"; return testName; } void benchmarkCompositeOp(const KoCompositeOp *op, bool haveMask, qreal opacity, qreal flow, const int srcAlignmentShift, const int dstAlignmentShift, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange) { QString testName = getTestName(haveMask, srcAlignmentShift, dstAlignmentShift, srcAlphaRange, dstAlphaRange); QVector tiles = generateTiles(numTiles, srcAlignmentShift, dstAlignmentShift, srcAlphaRange, dstAlphaRange, op->colorSpace()->pixelSize()); const int tileOffset = 4 * (processRect.y() * rowStride + processRect.x()); KoCompositeOp::ParameterInfo params; params.dstRowStride = 4 * rowStride; params.srcRowStride = 4 * rowStride; params.maskRowStride = rowStride; params.rows = processRect.height(); params.cols = processRect.width(); params.opacity = opacity; params.flow = flow; params.channelFlags = QBitArray(); QTime timer; timer.start(); Q_FOREACH (const Tile &tile, tiles) { params.dstRowStart = tile.dst + tileOffset; params.srcRowStart = tile.src + tileOffset; params.maskRowStart = haveMask ? tile.mask : 0; op->composite(params); } - dbgKrita << testName << "RESULT:" << timer.elapsed() << "msec"; + qDebug() << testName << "RESULT:" << timer.elapsed() << "msec"; freeTiles(tiles, srcAlignmentShift, dstAlignmentShift); } void benchmarkCompositeOp(const KoCompositeOp *op, const QString &postfix) { - dbgKrita << "Testing Composite Op:" << op->id() << "(" << postfix << ")"; + qDebug() << "Testing Composite Op:" << op->id() << "(" << postfix << ")"; benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, true, 0.5, 0.3, 8, 0, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 8, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, true, 0.5, 0.3, 4, 8, ALPHA_RANDOM, ALPHA_RANDOM); /// --- Vary the content of the source and destination benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_ZERO, ALPHA_RANDOM); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_UNIT, ALPHA_RANDOM); /// --- benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_RANDOM, ALPHA_ZERO); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_ZERO, ALPHA_ZERO); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_UNIT, ALPHA_ZERO); /// --- benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_RANDOM, ALPHA_UNIT); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_ZERO, ALPHA_UNIT); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_UNIT, ALPHA_UNIT); } #ifdef HAVE_VC template void checkRounding(qreal opacity, qreal flow, qreal averageOpacity = -1, quint32 pixelSize = 4) { QVector tiles = generateTiles(2, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM, pixelSize); const int vecSize = Vc::float_v::size(); const int numBlocks = numPixels / vecSize; quint8 *src1 = tiles[0].src; quint8 *dst1 = tiles[0].dst; quint8 *msk1 = tiles[0].mask; quint8 *src2 = tiles[1].src; quint8 *dst2 = tiles[1].dst; quint8 *msk2 = tiles[1].mask; KoCompositeOp::ParameterInfo params; params.opacity = opacity; params.flow = flow; if (averageOpacity >= 0.0) { params._lastOpacityData = averageOpacity; params.lastOpacity = ¶ms._lastOpacityData; } params.channelFlags = QBitArray(); typename Compositor::ParamsWrapper paramsWrapper(params); // The error count is needed as 38.5 gets rounded to 38 instead of 39 in the vc version. int errorcount = 0; for (int i = 0; i < numBlocks; i++) { Compositor::template compositeVector(src1, dst1, msk1, params.opacity, paramsWrapper); for (int j = 0; j < vecSize; j++) { //if (8 * i + j == 7080) { - // dbgKrita << "src: " << src2[0] << src2[1] << src2[2] << src2[3]; - // dbgKrita << "dst: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; - // dbgKrita << "msk:" << msk2[0]; + // qDebug() << "src: " << src2[0] << src2[1] << src2[2] << src2[3]; + // qDebug() << "dst: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; + // qDebug() << "msk:" << msk2[0]; //} Compositor::template compositeOnePixelScalar(src2, dst2, msk2, params.opacity, paramsWrapper); bool compareResult = true; if (pixelSize == 4) { compareResult = comparePixels(dst1, dst2, 0); if (!compareResult) { ++errorcount; compareResult = comparePixels(dst1, dst2, 1); if (!compareResult) { ++errorcount; } } } else if (pixelSize == 16) { compareResult = comparePixels(reinterpret_cast(dst1), reinterpret_cast(dst2), 0); } else { qFatal("Pixel size %i is not implemented", pixelSize); } if(!compareResult || errorcount > 1) { - dbgKrita << "Wrong rounding in pixel:" << 8 * i + j; - dbgKrita << "Vector version: " << dst1[0] << dst1[1] << dst1[2] << dst1[3]; - dbgKrita << "Scalar version: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; + qDebug() << "Wrong rounding in pixel:" << 8 * i + j; + qDebug() << "Vector version: " << dst1[0] << dst1[1] << dst1[2] << dst1[3]; + qDebug() << "Scalar version: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; - dbgKrita << "src:" << src1[0] << src1[1] << src1[2] << src1[3]; - dbgKrita << "msk:" << msk1[0]; + qDebug() << "src:" << src1[0] << src1[1] << src1[2] << src1[3]; + qDebug() << "msk:" << msk1[0]; QFAIL("Wrong rounding"); } src1 += pixelSize; dst1 += pixelSize; src2 += pixelSize; dst2 += pixelSize; msk1++; msk2++; } } freeTiles(tiles, 0, 0); } #endif void KisCompositionBenchmark::checkRoundingAlphaDarken_05_03() { #ifdef HAVE_VC - checkRounding >(0.5,0.3); + checkRounding >(0.5,0.3); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_05() { #ifdef HAVE_VC - checkRounding >(0.5,0.5); + checkRounding >(0.5,0.5); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_07() { #ifdef HAVE_VC - checkRounding >(0.5,0.7); + checkRounding >(0.5,0.7); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_10() { #ifdef HAVE_VC - checkRounding >(0.5,1.0); + checkRounding >(0.5,1.0); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_10_08() { #ifdef HAVE_VC - checkRounding >(0.5,1.0,0.8); + checkRounding >(0.5,1.0,0.8); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_03() { #ifdef HAVE_VC checkRounding >(0.5, 0.3, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_05() { #ifdef HAVE_VC checkRounding >(0.5, 0.5, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_07() { #ifdef HAVE_VC checkRounding >(0.5, 0.7, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_10() { #ifdef HAVE_VC checkRounding >(0.5, 1.0, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_10_08() { #ifdef HAVE_VC checkRounding >(0.5, 1.0, 0.8, 16); #endif } void KisCompositionBenchmark::checkRoundingOver() { #ifdef HAVE_VC checkRounding >(0.5, 0.3); #endif } void KisCompositionBenchmark::checkRoundingOverRgbaF32() { #ifdef HAVE_VC checkRounding >(0.5, 0.3, -1, 16); #endif } void KisCompositionBenchmark::compareAlphaDarkenOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); - KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); + KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs); KoCompositeOp *opExp = new KoCompositeOpAlphaDarken(cs); QVERIFY(compareTwoOps(true, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareRgbF32AlphaDarkenOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); - KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOp128(cs); + KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs); KoCompositeOp *opExp = new KoCompositeOpAlphaDarken(cs); QVERIFY(compareTwoOps(true, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareAlphaDarkenOpsNoMask() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); - KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); + KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs); KoCompositeOp *opExp = new KoCompositeOpAlphaDarken(cs); QVERIFY(compareTwoOps(false, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareOverOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp32(cs); KoCompositeOp *opExp = new KoCompositeOpOver(cs); QVERIFY(compareTwoOps(true, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareOverOpsNoMask() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp32(cs); KoCompositeOp *opExp = new KoCompositeOpOver(cs); QVERIFY(compareTwoOps(false, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareRgbF32OverOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp128(cs); KoCompositeOp *opExp = new KoCompositeOpOver(cs); QVERIFY(compareTwoOps(false, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = new KoCompositeOpAlphaDarken(cs); benchmarkCompositeOp(op, "Legacy"); delete op; } void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); - KoCompositeOp *op = KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); + KoCompositeOp *op = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs); benchmarkCompositeOp(op, "Optimized"); delete op; } void KisCompositionBenchmark::testRgb8CompositeOverLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = new KoCompositeOpOver(cs); benchmarkCompositeOp(op, "Legacy"); delete op; } void KisCompositionBenchmark::testRgb8CompositeOverOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp32(cs); benchmarkCompositeOp(op, "Optimized"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeAlphaDarkenLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = new KoCompositeOpAlphaDarken(cs); benchmarkCompositeOp(op, "Legacy"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeAlphaDarkenOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); - KoCompositeOp *op = KoOptimizedCompositeOpFactory::createAlphaDarkenOp128(cs); + KoCompositeOp *op = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs); benchmarkCompositeOp(op, "Optimized"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeOverLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = new KoCompositeOpOver(cs); benchmarkCompositeOp(op, "RGBF32 Legacy"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeOverOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp128(cs); benchmarkCompositeOp(op, "RGBF32 Optimized"); delete op; } void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenReal_Aligned() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp *op = cs->compositeOp(COMPOSITE_ALPHA_DARKEN); benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); } void KisCompositionBenchmark::testRgb8CompositeOverReal_Aligned() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp *op = cs->compositeOp(COMPOSITE_OVER); benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); } void KisCompositionBenchmark::testRgb8CompositeCopyLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp *op = cs->compositeOp(COMPOSITE_COPY); benchmarkCompositeOp(op, "Copy"); } void KisCompositionBenchmark::benchmarkMemcpy() { QVector tiles = generateTiles(numTiles, 0, 0, ALPHA_UNIT, ALPHA_UNIT, 4); QBENCHMARK_ONCE { Q_FOREACH (const Tile &tile, tiles) { memcpy(tile.dst, tile.src, 4 * numPixels); } } freeTiles(tiles, 0, 0); } #ifdef HAVE_VC const int vecSize = Vc::float_v::size(); const size_t uint8VecAlignment = qMax(vecSize * sizeof(quint8), sizeof(void*)); const size_t uint32VecAlignment = qMax(vecSize * sizeof(quint32), sizeof(void*)); const size_t floatVecAlignment = qMax(vecSize * sizeof(float), sizeof(void*)); #endif void KisCompositionBenchmark::benchmarkUintFloat() { #ifdef HAVE_VC using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint8VecAlignment, dataSize); if (error) { qFatal("posix_memalign failed: %d", error); } quint8 *iData = (quint8*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // convert uint -> float directly, this causes // static_cast helper be called Vc::float_v b(uint_v(iData + i)); b.store(fData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } void KisCompositionBenchmark::benchmarkUintIntFloat() { #ifdef HAVE_VC using int_v = Vc::SimdArray; using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint8VecAlignment, dataSize); if (error) { qFatal("posix_memalign failed: %d", error); } quint8 *iData = (quint8*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // convert uint->int->float, that avoids special sign // treating, and gives 2.6 times speedup Vc::float_v b(int_v(uint_v(iData + i))); b.store(fData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } void KisCompositionBenchmark::benchmarkFloatUint() { #ifdef HAVE_VC using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint32VecAlignment, dataSize * sizeof(quint32)); if (error) { qFatal("posix_memalign failed: %d", error); } quint32 *iData = (quint32*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // conversion float -> uint uint_v b(Vc::float_v(fData + i)); b.store(iData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } void KisCompositionBenchmark::benchmarkFloatIntUint() { #ifdef HAVE_VC using int_v = Vc::SimdArray; using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint32VecAlignment, dataSize * sizeof(quint32)); if (error) { qFatal("posix_memalign failed: %d", error); } quint32 *iData = (quint32*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // conversion float -> int -> uint uint_v b(int_v(Vc::float_v(fData + i))); b.store(iData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } QTEST_MAIN(KisCompositionBenchmark) diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index a5e23801e8..b2b8427fba 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,798 +1,817 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita +echo --cmd Launch a cmd prompt instead of building. +echo The environment is set up like the build +echo environment with some helper command macros. echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= +set ARG_CMD= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) + if "%1" == "--cmd" ( + set ARG_CMD=1 + set CURRENT_MATCHED=1 + ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%" ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\" ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=!PATH!;!GETTEXT_SEARCH_PATH! ) -if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps - -pushd %DEPS_BUILD_DIR% -if errorlevel 1 ( - echo ERROR: Cannot enter deps build dir! 1>&2 - exit /b 104 -) - -echo Running CMake for deps... -echo "%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ +:: Prepare the CMake command lines +set CMDLINE_CMAKE_DEPS="%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DUSE_QT_TABLET_WINDOWS=ON ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% - -"%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ - -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ - -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ - -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ - -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ - -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ - -G "MinGW Makefiles" ^ +set CMDLINE_CMAKE_KRITA="%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ + -DBoost_DEBUG=OFF ^ + -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ + -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ + -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ + -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ + -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ + -DBUILD_TESTING=OFF ^ + -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ + -DFOUNDATION_BUILD=ON ^ -DUSE_QT_TABLET_WINDOWS=ON ^ + -Wno-dev ^ + -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% + +:: Launch CMD prompt if requested +if "%ARG_CMD%" == "1" ( + doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%" + doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%" + doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*" + doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%" + echo. + title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^) + echo You're now in the build environment. + echo The following macros are available: + echo cmake-deps + echo -- Run CMake for the deps. + echo make-deps ^ + echo -- Run build for the specified deps target. The target name should + echo include the `ext_` prefix, e.g. `ext_qt`. + echo cmake-krita + echo -- Run CMake for Krita. + echo make-krita + echo -- Run build for Krita's `install` target. + echo. + echo For more info, type `doskey /macros` to view the macro commands. + cmd /k + exit +) + + +if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps + +pushd %DEPS_BUILD_DIR% if errorlevel 1 ( - echo ERROR: CMake configure failed! 1>&2 + echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) + +echo Running CMake for deps... + +@echo on +%CMDLINE_CMAKE_DEPS% +@if errorlevel 1 ( + @echo ERROR: CMake configure failed! 1>&2 + @exit /b 104 +) +@echo off echo. set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost eigen3 exiv2 fftw3 set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt set EXT_TARGETS=%EXT_TARGETS% quazip for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... -echo "%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ - -DBoost_DEBUG=OFF ^ - -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ - -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ - -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ - -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ - -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ - -DBUILD_TESTING=OFF ^ - -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ - -DFOUNDATION_BUILD=ON ^ - -DUSE_QT_TABLET_WINDOWS=ON ^ - -Wno-dev ^ - -G "MinGW Makefiles" ^ - -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% -"%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ - -DBoost_DEBUG=OFF ^ - -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ - -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ - -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ - -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ - -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ - -DBUILD_TESTING=OFF ^ - -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ - -DFOUNDATION_BUILD=ON ^ - -DUSE_QT_TABLET_WINDOWS=ON ^ - -Wno-dev ^ - -G "MinGW Makefiles" ^ - -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% -if errorlevel 1 ( - echo ERROR: CMake configure failed! 1>&2 - exit /b 104 +@echo on +%CMDLINE_CMAKE_KRITA% +@if errorlevel 1 ( + @echo ERROR: CMake configure failed! 1>&2 + @exit /b 104 ) +@echo off echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/krita/data/templates/animation/Anim-Jp-EN.desktop b/krita/data/templates/animation/Anim-Jp-EN.desktop index b022edb281..a01bb219f3 100644 --- a/krita/data/templates/animation/Anim-Jp-EN.desktop +++ b/krita/data/templates/animation/Anim-Jp-EN.desktop @@ -1,33 +1,33 @@ [Desktop Entry] Type=Link URL=.source/Anim-Jp-EN.kra Icon=template_animation Name=Animation-Japanese-En Name[ar]=حركة يابانية (إنجليزي) Name[ca]=Animació-Japonès-EN Name[ca@valencia]=Animació-Japonés-EN Name[de]=Animation-Japanisch-En Name[el]=Εφέ-κίνησης-Ιαπωνικό-En Name[en_GB]=Animation-Japanese-En Name[es]=Animación-Japonés-En Name[et]=Animation-Japanese-En Name[eu]=Animazioa-Japoniarra-En Name[fi]=Animaatio-japanilainen-EN Name[fr]=Animation japonaise (en) Name[gl]=Animación-xaponesa-en-inglés Name[is]=Hreyfimynd-Japanska-En Name[it]=Animazione-Giapponese-EN Name[ja]=日本式アニメ(英語版) Name[nl]=Animatie-Japans-En Name[nn]=Japansk animasjon – engelsk Name[pl]=Animacja-Japońska-En Name[pt]=Animação-Japonês-EN Name[pt_BR]=Animation-Japanese-En Name[ru]=Анимация-японская-англ Name[sk]=Animation-Japanese-En Name[sv]=Animering-japanska-en Name[tr]=Canlandırma-Japonca-İngilizce Name[uk]=Японська анімація (англійською) Name[x-test]=xxAnimation-Japanese-Enxx -Name[zh_CN]=日式动画 (英文图层名) +Name[zh_CN]=日式动画 (英语图层名) Name[zh_TW]=動畫-Japanese-En diff --git a/krita/data/templates/animation/Anim-Jp-JP.desktop b/krita/data/templates/animation/Anim-Jp-JP.desktop index d5d8ab7721..61b1cf3d4a 100644 --- a/krita/data/templates/animation/Anim-Jp-JP.desktop +++ b/krita/data/templates/animation/Anim-Jp-JP.desktop @@ -1,33 +1,33 @@ [Desktop Entry] Type=Link URL=.source/Anim-Jp-JP.kra Icon=template_animation Name=Animation-Japanese-JP Name[ar]=حركة يابانية (ياباني) Name[ca]=Animació-Japonès-JP Name[ca@valencia]=Animació-Japonés-JP Name[de]=Animation-Japanisch-JP Name[el]=Εφέ-κίνησης-Ιαπωνικό-JP Name[en_GB]=Animation-Japanese-JP Name[es]=Animación-Japonés-JP Name[et]=Animation-Japanese-JP Name[eu]=Animazioa-Japoniarra-JP Name[fi]=Animaatio-japanilainen-JP Name[fr]=Animation japonaise (jp) Name[gl]=Animación-xaponesa-en-xaponés Name[is]=Hreyfimynd-Japanska-JP Name[it]=Animazione-Giapponese-JP Name[ja]=日本式アニメ(日本語版) Name[nl]=Animatie-Japans-JP Name[nn]=Japansk animasjon – japansk Name[pl]=Animacja-Japońska-JP Name[pt]=Animação-Japonês-JP Name[pt_BR]=Animation-Japanese-JP Name[ru]=Анимация-японская-японск Name[sk]=Animation-Japanese-JP Name[sv]=Animering-japanska-jp Name[tr]=Canlandırma-Japonca-JP Name[uk]=Японська анімація (японською) Name[x-test]=xxAnimation-Japanese-JPxx -Name[zh_CN]=日本动画 (日式) +Name[zh_CN]=日式动画 (日语图层名) Name[zh_TW]=動畫-Japanese-JP diff --git a/libs/command/kis_command_utils.cpp b/libs/command/kis_command_utils.cpp index d53a4a65ab..5a78d82b83 100644 --- a/libs/command/kis_command_utils.cpp +++ b/libs/command/kis_command_utils.cpp @@ -1,205 +1,207 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_command_utils.h" namespace KisCommandUtils { AggregateCommand::AggregateCommand(KUndo2Command *parent) : KUndo2Command(parent), m_firstRedo(true) {} AggregateCommand::AggregateCommand(const KUndo2MagicString &text, KUndo2Command *parent) : KUndo2Command(text, parent), m_firstRedo(true) {} void AggregateCommand::redo() { if (m_firstRedo) { m_firstRedo = false; populateChildCommands(); } m_store.redoAll(); } void AggregateCommand::undo() { m_store.undoAll(); } void AggregateCommand::addCommand(KUndo2Command *cmd) { if (!cmd) return; m_store.addCommand(cmd); } LambdaCommand::LambdaCommand(std::function createCommandFunc) : m_createCommandFunc(createCommandFunc) { } LambdaCommand::LambdaCommand(const KUndo2MagicString &text, std::function createCommandFunc) : AggregateCommand(text), m_createCommandFunc(createCommandFunc) { } LambdaCommand::LambdaCommand(const KUndo2MagicString &text, KUndo2Command *parent, std::function createCommandFunc) : AggregateCommand(text, parent), m_createCommandFunc(createCommandFunc) { } LambdaCommand::LambdaCommand(KUndo2Command *parent, std::function createCommandFunc) : AggregateCommand(parent), m_createCommandFunc(createCommandFunc) { } void LambdaCommand::populateChildCommands() { if (m_createCommandFunc) { addCommand(m_createCommandFunc()); } } SkipFirstRedoWrapper::SkipFirstRedoWrapper(KUndo2Command *child, KUndo2Command *parent) : KUndo2Command(child ? child->text() : kundo2_noi18n(""), parent), m_firstRedo(true), m_child(child) {} void SkipFirstRedoWrapper::redo() { if (m_firstRedo) { m_firstRedo = false; } else { if (m_child) { m_child->redo(); } KUndo2Command::redo(); } } void SkipFirstRedoWrapper::undo() { KUndo2Command::undo(); if (m_child) { m_child->undo(); } } SkipFirstRedoBase::SkipFirstRedoBase(bool skipFirstRedo, KUndo2Command *parent) : KUndo2Command(parent), m_firstRedo(skipFirstRedo) { } SkipFirstRedoBase::SkipFirstRedoBase(bool skipFirstRedo, const KUndo2MagicString &text, KUndo2Command *parent) : KUndo2Command(text, parent), m_firstRedo(skipFirstRedo) { } void SkipFirstRedoBase::redo() { if (m_firstRedo) { m_firstRedo = false; } else { redoImpl(); KUndo2Command::redo(); } } void SkipFirstRedoBase::undo() { KUndo2Command::undo(); undoImpl(); } void SkipFirstRedoBase::setSkipOneRedo(bool value) { m_firstRedo = value; } FlipFlopCommand::FlipFlopCommand(bool finalizing, KUndo2Command *parent) : KUndo2Command(parent), m_firstRedo(true) { m_currentState = finalizing ? State::FINALIZING : State::INITIALIZING; } FlipFlopCommand::FlipFlopCommand(State initialState, KUndo2Command *parent) : KUndo2Command(parent), m_currentState(initialState) {} void FlipFlopCommand::redo() { if (m_currentState == FlipFlopCommand::State::INITIALIZING) { partA(); } else { partB(); } m_firstRedo = false; } void FlipFlopCommand::undo() { if (m_currentState == FlipFlopCommand::State::FINALIZING) { partA(); } else { partB(); } } void FlipFlopCommand::partA() {} void FlipFlopCommand::partB() {} CompositeCommand::CompositeCommand(KUndo2Command *parent) : KUndo2Command(parent) {} CompositeCommand::~CompositeCommand() { qDeleteAll(m_commands); } void CompositeCommand::addCommand(KUndo2Command *cmd) { if (cmd) { m_commands << cmd; } } void CompositeCommand::redo() { + KUndo2Command::redo(); Q_FOREACH (KUndo2Command *cmd, m_commands) { cmd->redo(); } } void CompositeCommand::undo() { + KUndo2Command::undo(); Q_FOREACH (KUndo2Command *cmd, m_commands) { cmd->undo(); } } } diff --git a/libs/image/kis_colorspace_convert_visitor.cpp b/libs/image/kis_colorspace_convert_visitor.cpp index c56b3e85ed..ce003e8967 100644 --- a/libs/image/kis_colorspace_convert_visitor.cpp +++ b/libs/image/kis_colorspace_convert_visitor.cpp @@ -1,160 +1,155 @@ /* * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorspace_convert_visitor.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_undo_adapter.h" #include "kis_adjustment_layer.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_external_layer_iface.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" #include "kis_generator.h" #include "kis_generator_registry.h" #include "generator/kis_generator_layer.h" #include "kis_time_range.h" #include KisColorSpaceConvertVisitor::KisColorSpaceConvertVisitor(KisImageWSP image, const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KisNodeVisitor() , m_image(image) , m_srcColorSpace(srcColorSpace) , m_dstColorSpace(dstColorSpace) , m_renderingIntent(renderingIntent) , m_conversionFlags(conversionFlags) { } KisColorSpaceConvertVisitor::~KisColorSpaceConvertVisitor() { } bool KisColorSpaceConvertVisitor::visit(KisGroupLayer * layer) { convertPaintDevice(layer); KisLayerSP child = qobject_cast(layer->firstChild().data()); while (child) { child->accept(*this); child = qobject_cast(child->nextSibling().data()); } layer->resetCache(); return true; } bool KisColorSpaceConvertVisitor::visit(KisPaintLayer *layer) { return convertPaintDevice(layer); } bool KisColorSpaceConvertVisitor::visit(KisGeneratorLayer *layer) { layer->resetCache(); return true; } bool KisColorSpaceConvertVisitor::visit(KisAdjustmentLayer * layer) { // XXX: Make undoable! if (layer->filter()->name() == "perchannel") { // Per-channel filters need to be reset because of different number // of channels. This makes undo very tricky, but so be it. // XXX: Make this more generic for after 1.6, when we'll have many // channel-specific filters. KisFilterSP f = KisFilterRegistry::instance()->value("perchannel"); layer->setFilter(f->defaultConfiguration()); } layer->resetCache(); return true; } bool KisColorSpaceConvertVisitor::convertPaintDevice(KisLayer* layer) { if (*m_dstColorSpace == *layer->colorSpace()) return true; bool alphaLock = false; if (m_srcColorSpace->colorModelId() != m_dstColorSpace->colorModelId()) { layer->setChannelFlags(m_emptyChannelFlags); KisPaintLayer *paintLayer = 0; if ((paintLayer = dynamic_cast(layer))) { alphaLock = paintLayer->alphaLocked(); paintLayer->setChannelLockFlags(QBitArray()); } } KisImageSP image = m_image.toStrongRef(); if (!image) { return false; } + KUndo2Command *parentConversionCommand = new KUndo2Command(); + if (layer->original()) { - KUndo2Command* cmd = layer->original()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags); - if (cmd) { - image->undoAdapter()->addCommand(cmd); - } + layer->original()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags, parentConversionCommand); } if (layer->paintDevice()) { - KUndo2Command* cmd = layer->paintDevice()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags); - if (cmd) { - image->undoAdapter()->addCommand(cmd); - } + layer->paintDevice()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags, parentConversionCommand); } if (layer->projection()) { - KUndo2Command* cmd = layer->projection()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags); - if (cmd) { - image->undoAdapter()->addCommand(cmd); - } + layer->projection()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags, parentConversionCommand); } + image->undoAdapter()->addCommand(parentConversionCommand); + KisPaintLayer *paintLayer = 0; if ((paintLayer = dynamic_cast(layer))) { paintLayer->setAlphaLocked(alphaLock); } layer->setDirty(); layer->invalidateFrames(KisTimeRange::infinite(0), layer->extent()); return true; } bool KisColorSpaceConvertVisitor::visit(KisColorizeMask *mask) { KisImageSP image = m_image.toStrongRef(); if (!image) { return false; } KUndo2Command* cmd = mask->setColorSpace(m_dstColorSpace, m_renderingIntent, m_conversionFlags); if (cmd) { image->undoAdapter()->addCommand(cmd); } return true; } diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index 2ca0518331..3f541a94a8 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1538 +1,1531 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_utils.h" #include #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "commands/kis_image_change_visibility_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include "commands/kis_node_compositeop_command.h" #include #include "krita_utils.h" #include "kis_image_signal_router.h" namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = qobject_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; bool useInTimeline = false; bool enableOnionSkins = false; virtual KisNodeList allSrcNodes() = 0; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(currLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); paintLayer = qobject_cast(prevLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); useInTimeline |= node->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(node.data()); if (paintLayer) { enableOnionSkins |= paintLayer->onionSkinEnabled(); } } } KisNodeList mergedNodes; bool nodesCompositingVaries = false; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand { DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { /** * We disable extra compositing only in case all the layers have * the same compositing properties, therefore, we can just sum them using * Normal blend mode */ if (m_info->nodesCompositingVaries) return; // we should disable dirty requests on **redo only**, otherwise // the state of the layers will not be recovered on undo m_info->image->disableDirtyRequests(); Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (node->compositeOpId() != COMPOSITE_OVER) { addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER)); } if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::inheritAlpha, false); addCommand(new KisNodePropertyListCommand(node, props)); } } m_info->image->enableDirtyRequests(); } private: MergeMultipleInfoSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { forceAllDelayedNodesUpdate(node); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } m_info->dstNode->setUseInTimeline(m_info->useInTimeline); KisPaintLayer *dstPaintLayer = qobject_cast(m_info->dstNode.data()); if (dstPaintLayer) { dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); m_info->dstNode = dstPaintLayer; if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } auto channelFlagsLazy = [](KisNodeSP node) { KisLayer *layer = dynamic_cast(node.data()); return layer ? layer->channelFlags() : QBitArray(); }; QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; bool isFirstCycle = true; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (isFirstCycle) { compositeOpId = node->compositeOpId(); channelFlags = channelFlagsLazy(node); isFirstCycle = false; } else if (compositeOpId != node->compositeOpId() || channelFlags != channelFlagsLazy(node)) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } m_info->nodesCompositingVaries = compositionVaries; m_info->dstNode->setUseInTimeline(m_info->useInTimeline); dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::partB() { KisImageSignalType type; if (getState() == State::FINALIZING) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } SelectGlobalSelectionMask::SelectGlobalSelectionMask(KisImageSP image) : m_image(image) { } void SelectGlobalSelectionMask::redo() { KisImageSignalType type = ComplexNodeReselectionSignal(m_image->rootLayer()->selectionMask(), KisNodeList()); m_image->signalRouter()->emitNotification(type); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); auto isNodeWeird = [] (KisNodeSP node) { const bool normalCompositeMode = node->compositeOpId() == COMPOSITE_OVER; KisLayer *layer = dynamic_cast(node.data()); const bool hasInheritAlpha = layer && layer->alphaChannelDisabled(); return !normalCompositeMode && !hasInheritAlpha; }; while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, !isNodeWeird(node), true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node // -- checking all parents if they are included in nodesToDelete // Not every descendant is included in nodesToDelete even if in fact // they are going to be deleted, so we need to check it. // If we consider the path from root to the putAfter node, // if there are any nodes marked for deletion, any node afterwards // is going to be deleted, too. // example: root . . . . . ! ! . . ! ! ! ! . . . . putAfter // it should be: root . . . . . ! ! ! ! ! ! ! ! ! ! ! ! !putAfter // and here: root . . . . X ! ! . . ! ! ! ! . . . . putAfter // you can see which node is "the perfect ancestor" // (marked X; called "parent" in the function arguments). + // and here: root . . . . . O ! . . ! ! ! ! . . . . putAfter + // you can see which node is "the topmost deleted ancestor" (marked 'O') KisNodeSP node = putAfter->parent(); bool foundDeletedAncestor = false; - KisNodeSP lastPerfectAncestor = nullptr; + KisNodeSP topmostAncestorToDelete = nullptr; while (node) { if (nodesToDelete.contains(node) && !nodesToDelete.contains(node->parent())) { foundDeletedAncestor = true; - lastPerfectAncestor = node->parent(); + topmostAncestorToDelete = node; // Here node is to be deleted and its parent is not, // so its parent is the one of the first not deleted (="perfect") ancestors. // We need the one that is closest to the top (root) } node = node->parent(); } if (foundDeletedAncestor) { - parent = lastPerfectAncestor; + parent = topmostAncestorToDelete->parent(); + putAfter = topmostAncestorToDelete; } else { parent = putAfter->parent(); // putAfter (and none of its ancestors) is to be deleted, so its parent is the first not deleted ancestor } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { - KisGroupLayerSP oldRoot = m_info->image->rootLayer(); - KisGroupLayerSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); - newRoot->setDefaultProjectionColor(oldRoot->defaultProjectionColor()); - + KisNodeSP oldRoot = m_info->image->root(); + KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { - if (parent == m_putAfter->parent()) { - addCommand(new KisImageLayerAddCommand(m_info->image, - m_info->dstNode, - parent, - m_putAfter, - true, false)); - } - else { - addCommand(new KisImageLayerAddCommand(m_info->image, - m_info->dstNode, - parent, - parent->lastChild(), - true, false)); - } + addCommand(new KisImageLayerAddCommand(m_info->image, + m_info->dstNode, + parent, + m_putAfter, + true, false)); + /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } KisNodeList safeNodesToDelete = m_info->allSrcNodes(); for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) { KisNodeSP node = *it; if (node->userLocked() && node->visible()) { addCommand(new KisImageChangeVisibilityCommand(false, node)); } } KritaUtils::filterContainer(safeNodesToDelete, [](KisNodeSP node) { return !node->userLocked(); }); safeRemoveMultipleNodes(safeNodesToDelete, m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::partA() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::partB() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd); applyKeyframeColorLabel(keyframe); addCommand(cmd); } void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) { Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) { Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisKeyframeSP keyframe = channel->keyframeAt(m_frame); if (!keyframe.isNull() && keyframe->colorLabel() != 0) { dstKeyframe->setColorLabel(keyframe->colorLabel()); return; } } } dstKeyframe->setColorLabel(0); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // NOTE: shape layer may have emitted spontaneous jobs during layer creation, // wait for them to complete! applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass through only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { //qDebug() << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible() || node->userLocked()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { /* If the putAfter node is invisible, * we should instead pick one of the nodes * to be merged to avoid a null putAfter. */ if (!putAfter->visible()){ putAfter = mergedNodes.first(); } applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); applicator.applyCommand(new DisableExtraCompositing(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image, KisNodeSP activeNode) { if (!activeNode) { activeNode = image->root()->lastChild(); } KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, activeNode, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::partB() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } void forceAllDelayedNodesUpdate(KisNodeSP root) { KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } KisImageSP findImageByHierarchy(KisNodeSP node) { while (node) { const KisLayer *layer = dynamic_cast(node.data()); if (layer) { return layer->image(); } node = node->parent(); } return 0; } } diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index 884fdb66f0..9ccb6d821a 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2220 +1,2214 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); - KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); + void convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand); bool assignProfile(const KoColorProfile * profile); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(q, rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(q, rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(q, it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(q, rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(q, m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(q, srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(q, srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; void updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(q, m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(q, srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0); Data *srcData = currentNonLodData(); Data *lodData = new Data(q, srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod) { const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcDataManager->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(lodData->x(), lodData->y()), originalRect, lod); } void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst)); Data *srcData = currentNonLodData(); updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(dst->x(), dst->y()), originalRect, lod); } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(q, srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } -KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) +void KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { class DeviceChangeColorSpaceCommand : public KUndo2Command { public: - DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) - : m_firstRun(true), + DeviceChangeColorSpaceCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) + : KUndo2Command(parent), + m_firstRun(true), m_device(device) { } void emitNotifications() { m_device->emitColorSpaceChanged(); m_device->setDirty(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } private: bool m_firstRun; KisPaintDeviceSP m_device; }; - KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q); - QList dataObjects = allDataObjects(); + if (dataObjects.isEmpty()) return; + + KUndo2Command *mainCommand = + parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; - data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); - } - - if (!parentCommand->childCount()) { - delete parentCommand; - parentCommand = 0; - } else { - q->emitColorSpaceChanged(); + data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, mainCommand); } - return parentCommand; - + q->emitColorSpaceChanged(); } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { makeFullCopyFrom(rhs, copyMode, newParentNode); } } void KisPaintDevice::makeFullCopyFrom(const KisPaintDevice &rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } -KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) +void KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { - KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags); - return command; + m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } bool KisPaintDevice::setProfile(const KoColorProfile * profile) { return m_d->assignProfile(profile); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); const QRect r = selection->selectedExactRect(); if (r.isValid()) { { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } } // purge() must be executed **after** all iterators have been destroyed! m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { if (m_d->contentChannel) { return m_d->contentChannel.data(); } return 0; } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { m_d->generateLodCloneDevice(dst, originalRect, lod); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h index 2e342e6919..ab325b6158 100644 --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -1,896 +1,895 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisDataManager; class KisPaintDeviceWriter; class KisKeyframe; class KisRasterKeyframeChannel; class KisPaintDeviceFramesInterface; typedef KisSharedPtr KisDataManagerSP; namespace KritaUtils { enum DeviceCopyMode { CopySnapshot = 0, CopyAllFrames }; } /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x, y position * (it is not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString()); /** * Creates a copy of this device. * * If \p copyMode is CopySnapshot, the newly created device clones the * current frame of \p rhs only (default and efficient * behavior). If \p copyFrames is CopyAllFrames, the new device is a deep * copy of the source with all the frames included. */ KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); ~KisPaintDevice() override; void makeFullCopyFrom(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel is not completely transparent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (no incremental move) */ void moveTo(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void moveTo(const QPoint& pt); /** * Return an X,Y offset of the device in a convenient form */ QPoint offset() const; /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convenience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Relaxed version of the exactBounds() that can be used in tight * loops. If the exact bounds value is present in the paint * device cache, returns this value. If the cache is invalidated, * returns extent() and tries to recalculate the exact bounds not * faster than once in 1000 ms. */ QRect exactBoundsAmortized() const; /** * Returns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ QRegion region() const; /** * The slow version of region() that searches for exact bounds of * each rectangle in the region */ QRegion regionExact() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convenience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the image will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. * If frame ID is given, set default pixel for that frame. Otherwise use active frame. */ void setDefaultPixel(const KoColor &defPixel); /** * Get a pointer to the default pixel. * If the frame parameter is given, get the default pixel of * specified frame. Otherwise use currently active frame. */ KoColor defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt operation. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coincide, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace - * - * @return a command that can be used to undo the conversion. */ - KUndo2Command* convertTo(const KoColorSpace * dstColorSpace, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + void convertTo(const KoColorSpace * dstColorSpace, + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(), + KUndo2Command *parentCommand = 0); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ bool setProfile(const KoColorProfile * profile); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param image the image * @param profile name of the RGB profile to interpret the image as. 0 is interpreted as sRGB * @param offsetX x offset * @param offsetY y offset */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param w maximum width * @param h maximum height * @param rect only this rect will be used for the thumbnail * @param outputRect output rectangle * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * @param oversample: ratio used for antialiasing * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convenience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * transaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; /** * Create a keyframe channel for the content on this device. * @param id identifier for the channel * @return keyframe channel or 0 if there is not one */ KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id); KisRasterKeyframeChannel* keyframeChannel() const; /** * An interface to modify/load/save frames stored inside this device */ KisPaintDeviceFramesInterface* framesInterface(); public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); /** * Add the specified region to the parent layer's dirty region * (if there is a parent layer) */ void setDirty(const QRegion & region); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector rects); /** * Called by KisTransactionData when it thinks current time should * be changed. And the requests is forwarded to the image if * needed. */ void requestTimeSwitch(int time); /** * \return a sequence number corresponding to the current paint * device state. Every time the paint device is changed, * the sequence number is increased */ int sequenceNumber() const; void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const; public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y); KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param w width of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param h height of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject() override; }; static MemoryReleaseObject* createMemoryReleaseObject(); public: struct LodDataStruct { virtual ~LodDataStruct(); }; QRegion regionForLodSyncing() const; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void setProjectionDevice(bool value); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; void emitColorSpaceChanged(); void emitProfileChanged(); private: friend class KisPaintDeviceFramesInterface; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/libs/image/kis_paint_device_data.h b/libs/image/kis_paint_device_data.h index 79d31bbb5d..8b97cae212 100644 --- a/libs/image/kis_paint_device_data.h +++ b/libs/image/kis_paint_device_data.h @@ -1,286 +1,289 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PAINT_DEVICE_DATA_H #define __KIS_PAINT_DEVICE_DATA_H #include "KoAlwaysInline.h" #include "kundo2command.h" struct DirectDataAccessPolicy { DirectDataAccessPolicy(KisDataManager *dataManager, KisIteratorCompleteListener *completionListener) : m_dataManager(dataManager), m_completionListener(completionListener){} KisHLineConstIteratorSP createConstIterator(const QRect &rect) { const int xOffset = 0; const int yOffset = 0; return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, false, m_completionListener); } KisHLineIteratorSP createIterator(const QRect &rect) { const int xOffset = 0; const int yOffset = 0; return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, true, m_completionListener); } int pixelSize() const { return m_dataManager->pixelSize(); } KisDataManager *m_dataManager; KisIteratorCompleteListener *m_completionListener; }; class KisPaintDeviceData { public: KisPaintDeviceData(KisPaintDevice *paintDevice) : m_cache(paintDevice), m_x(0), m_y(0), m_colorSpace(0), m_levelOfDetail(0), m_cacheInvalidator(this) { } KisPaintDeviceData(KisPaintDevice *paintDevice, const KisPaintDeviceData *rhs, bool cloneContent) : m_dataManager(cloneContent ? new KisDataManager(*rhs->m_dataManager) : new KisDataManager(rhs->m_dataManager->pixelSize(), rhs->m_dataManager->defaultPixel())), m_cache(paintDevice), m_x(rhs->m_x), m_y(rhs->m_y), m_colorSpace(rhs->m_colorSpace), m_levelOfDetail(rhs->m_levelOfDetail), m_cacheInvalidator(this) { m_cache.setupCache(); } void init(const KoColorSpace *cs, KisDataManagerSP dataManager) { m_colorSpace = cs; m_dataManager = dataManager; m_cache.setupCache(); } class ChangeColorSpaceCommand : public KUndo2Command { public: ChangeColorSpaceCommand(KisPaintDeviceData *data, KisDataManagerSP oldDm, KisDataManagerSP newDm, const KoColorSpace *oldCs, const KoColorSpace *newCs, KUndo2Command *parent) : KUndo2Command(parent), m_firstRun(true), m_data(data), m_oldDm(oldDm), m_newDm(newDm), m_oldCs(oldCs), m_newCs(newCs) { } void forcedRedo() { m_data->m_dataManager = m_newDm; m_data->m_colorSpace = m_newCs; m_data->m_cache.setupCache(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } forcedRedo(); } void undo() override { m_data->m_dataManager = m_oldDm; m_data->m_colorSpace = m_oldCs; m_data->m_cache.setupCache(); KUndo2Command::undo(); } private: bool m_firstRun; KisPaintDeviceData *m_data; KisDataManagerSP m_oldDm; KisDataManagerSP m_newDm; const KoColorSpace *m_oldCs; const KoColorSpace *m_newCs; }; void assignColorSpace(const KoColorSpace *dstColorSpace) { KIS_ASSERT_RECOVER_RETURN(m_colorSpace->pixelSize() == dstColorSpace->pixelSize()); m_colorSpace = dstColorSpace; m_cache.invalidate(); } void convertDataColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { typedef KisSequentialIteratorBase, DirectDataAccessPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, DirectDataAccessPolicy> InternalSequentialIterator; if (m_colorSpace == dstColorSpace || *m_colorSpace == *dstColorSpace) { return; } QRect rc = m_dataManager->region().boundingRect(); const int dstPixelSize = dstColorSpace->pixelSize(); QScopedArrayPointer dstDefaultPixel(new quint8[dstPixelSize]); memset(dstDefaultPixel.data(), 0, dstPixelSize); m_colorSpace->convertPixelsTo(m_dataManager->defaultPixel(), dstDefaultPixel.data(), dstColorSpace, 1, renderingIntent, conversionFlags); KisDataManagerSP dstDataManager = new KisDataManager(dstPixelSize, dstDefaultPixel.data()); if (!rc.isEmpty()) { InternalSequentialConstIterator srcIt(DirectDataAccessPolicy(m_dataManager.data(), cacheInvalidator()), rc); InternalSequentialIterator dstIt(DirectDataAccessPolicy(dstDataManager.data(), cacheInvalidator()), rc); int nConseqPixels = srcIt.nConseqPixels(); // since we are accessing data managers directly, the columns are always aligned KIS_SAFE_ASSERT_RECOVER_NOOP(srcIt.nConseqPixels() == dstIt.nConseqPixels()); while(srcIt.nextPixels(nConseqPixels) && dstIt.nextPixels(nConseqPixels)) { nConseqPixels = srcIt.nConseqPixels(); const quint8 *srcData = srcIt.rawDataConst(); quint8 *dstData = dstIt.rawData(); m_colorSpace->convertPixelsTo(srcData, dstData, dstColorSpace, nConseqPixels, renderingIntent, conversionFlags); } } // becomes owned by the parent ChangeColorSpaceCommand *cmd = new ChangeColorSpaceCommand(this, m_dataManager, dstDataManager, m_colorSpace, dstColorSpace, parentCommand); cmd->forcedRedo(); + if (!parentCommand) { + delete cmd; + } } void prepareClone(const KisPaintDeviceData *srcData, bool copyContent = false) { m_x = srcData->x(); m_y = srcData->y(); if (copyContent) { m_dataManager = new KisDataManager(*srcData->dataManager()); } else if (m_dataManager->pixelSize() != srcData->dataManager()->pixelSize()) { // NOTE: we don't check default pixel value! it is the task of // the higher level! m_dataManager = new KisDataManager(srcData->dataManager()->pixelSize(), srcData->dataManager()->defaultPixel()); m_cache.setupCache(); } else { m_dataManager->clear(); const quint8 *srcDefPixel = srcData->dataManager()->defaultPixel(); const int cmp = memcmp(srcDefPixel, m_dataManager->defaultPixel(), m_dataManager->pixelSize()); if (cmp != 0) { m_dataManager->setDefaultPixel(srcDefPixel); } } m_levelOfDetail = srcData->levelOfDetail(); m_colorSpace = srcData->colorSpace(); m_cache.invalidate(); } ALWAYS_INLINE KisDataManagerSP dataManager() const { return m_dataManager; } ALWAYS_INLINE KisPaintDeviceCache* cache() { return &m_cache; } ALWAYS_INLINE qint32 x() const { return m_x; } ALWAYS_INLINE void setX(qint32 value) { m_x = value; } ALWAYS_INLINE qint32 y() const { return m_y; } ALWAYS_INLINE void setY(qint32 value) { m_y = value; } ALWAYS_INLINE const KoColorSpace* colorSpace() const { return m_colorSpace; } ALWAYS_INLINE qint32 levelOfDetail() const { return m_levelOfDetail; } ALWAYS_INLINE void setLevelOfDetail(qint32 value) { m_levelOfDetail = value; } ALWAYS_INLINE KisIteratorCompleteListener* cacheInvalidator() { return &m_cacheInvalidator; } private: struct CacheInvalidator : public KisIteratorCompleteListener { CacheInvalidator(KisPaintDeviceData *_q) : q(_q) {} void notifyWritableIteratorCompleted() override { q->cache()->invalidate(); } private: KisPaintDeviceData *q; }; private: KisDataManagerSP m_dataManager; KisPaintDeviceCache m_cache; qint32 m_x; qint32 m_y; const KoColorSpace* m_colorSpace; qint32 m_levelOfDetail; CacheInvalidator m_cacheInvalidator; }; #endif /* __KIS_PAINT_DEVICE_DATA_H */ diff --git a/libs/image/kis_pixel_selection.cpp b/libs/image/kis_pixel_selection.cpp index a7f838ae7c..53b43d40e7 100644 --- a/libs/image/kis_pixel_selection.cpp +++ b/libs/image/kis_pixel_selection.cpp @@ -1,600 +1,600 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_pixel_selection.h" #include #include #include #include #include #include #include #include #include #include #include "kis_layer.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_fill_painter.h" #include "kis_outline_generator.h" #include #include "kis_lod_transform.h" #include "kundo2command.h" struct Q_DECL_HIDDEN KisPixelSelection::Private { KisSelectionWSP parentSelection; QPainterPath outlineCache; bool outlineCacheValid; QMutex outlineCacheMutex; bool thumbnailImageValid; QImage thumbnailImage; QTransform thumbnailImageTransform; QPoint lod0CachesOffset; void invalidateThumbnailImage() { thumbnailImageValid = false; thumbnailImage = QImage(); thumbnailImageTransform = QTransform(); } }; KisPixelSelection::KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds, KisSelectionWSP parentSelection) : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), defaultBounds) , m_d(new Private) { m_d->outlineCacheValid = true; m_d->invalidateThumbnailImage(); m_d->parentSelection = parentSelection; } KisPixelSelection::KisPixelSelection(const KisPixelSelection& rhs, KritaUtils::DeviceCopyMode copyMode) : KisPaintDevice(rhs, copyMode) , KisSelectionComponent(rhs) , m_d(new Private) { // parent selection is not supposed to be shared m_d->outlineCache = rhs.m_d->outlineCache; m_d->outlineCacheValid = rhs.m_d->outlineCacheValid; m_d->thumbnailImageValid = rhs.m_d->thumbnailImageValid; m_d->thumbnailImage = rhs.m_d->thumbnailImage; m_d->thumbnailImageTransform = rhs.m_d->thumbnailImageTransform; } KisPixelSelection::KisPixelSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode, KisSelectionWSP parentSelection) : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), copySource->defaultBounds()) , m_d(new Private) { KisPaintDeviceSP tmpDevice = new KisPaintDevice(*copySource, copyMode, 0); - delete tmpDevice->convertTo(this->colorSpace()); + tmpDevice->convertTo(this->colorSpace()); this->makeFullCopyFrom(*tmpDevice, copyMode, 0); m_d->parentSelection = parentSelection; m_d->outlineCacheValid = true; m_d->invalidateThumbnailImage(); } KisSelectionComponent* KisPixelSelection::clone(KisSelection*) { return new KisPixelSelection(*this); } KisPixelSelection::~KisPixelSelection() { delete m_d; } const KoColorSpace *KisPixelSelection::compositionSourceColorSpace() const { return KoColorSpaceRegistry::instance()-> colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()); } bool KisPixelSelection::read(QIODevice *stream) { bool retval = KisPaintDevice::read(stream); m_d->outlineCacheValid = false; m_d->invalidateThumbnailImage(); return retval; } void KisPixelSelection::select(const QRect & rc, quint8 selectedness) { QRect r = rc.normalized(); if (r.isEmpty()) return; KisFillPainter painter(KisPaintDeviceSP(this)); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); painter.fillRect(r, KoColor(Qt::white, cs), selectedness); if (m_d->outlineCacheValid) { QPainterPath path; path.addRect(r); if (selectedness != MIN_SELECTED) { m_d->outlineCache += path; } else { m_d->outlineCache -= path; } } m_d->invalidateThumbnailImage(); } void KisPixelSelection::applySelection(KisPixelSelectionSP selection, SelectionAction action) { switch (action) { case SELECTION_REPLACE: clear(); addSelection(selection); break; case SELECTION_ADD: addSelection(selection); break; case SELECTION_SUBTRACT: subtractSelection(selection); break; case SELECTION_INTERSECT: intersectSelection(selection); break; case SELECTION_SYMMETRICDIFFERENCE: symmetricdifferenceSelection(selection); break; default: break; } } void KisPixelSelection::copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect) { const KoColorSpace *srcCS = src->colorSpace(); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(this, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); } m_d->outlineCacheValid = false; m_d->outlineCache = QPainterPath(); m_d->invalidateThumbnailImage(); } void KisPixelSelection::addSelection(KisPixelSelectionSP selection) { QRect r = selection->selectedRect(); if (r.isEmpty()) return; KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); for (int i = 0; i < r.height(); ++i) { do { if (*src->oldRawData() + *dst->rawData() < MAX_SELECTED) *dst->rawData() = *src->oldRawData() + *dst->rawData(); else *dst->rawData() = MAX_SELECTED; } while (src->nextPixel() && dst->nextPixel()); dst->nextRow(); src->nextRow(); } m_d->outlineCacheValid &= selection->outlineCacheValid(); if (m_d->outlineCacheValid) { m_d->outlineCache += selection->outlineCache(); } m_d->invalidateThumbnailImage(); } void KisPixelSelection::subtractSelection(KisPixelSelectionSP selection) { QRect r = selection->selectedRect(); if (r.isEmpty()) return; KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); for (int i = 0; i < r.height(); ++i) { do { if (*dst->rawData() - *src->oldRawData() > MIN_SELECTED) *dst->rawData() = *dst->rawData() - *src->oldRawData(); else *dst->rawData() = MIN_SELECTED; } while (src->nextPixel() && dst->nextPixel()); dst->nextRow(); src->nextRow(); } m_d->outlineCacheValid &= selection->outlineCacheValid(); if (m_d->outlineCacheValid) { m_d->outlineCache -= selection->outlineCache(); } m_d->invalidateThumbnailImage(); } void KisPixelSelection::intersectSelection(KisPixelSelectionSP selection) { QRect r = selection->selectedRect().united(selectedRect()); if (r.isEmpty()) return; KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); for (int i = 0; i < r.height(); ++i) { do { *dst->rawData() = qMin(*dst->rawData(), *src->oldRawData()); } while (src->nextPixel() && dst->nextPixel()); dst->nextRow(); src->nextRow(); } m_d->outlineCacheValid &= selection->outlineCacheValid(); if (m_d->outlineCacheValid) { m_d->outlineCache &= selection->outlineCache(); } m_d->invalidateThumbnailImage(); } void KisPixelSelection::symmetricdifferenceSelection(KisPixelSelectionSP selection) { QRect r = selection->selectedRect().united(selectedRect()); if (r.isEmpty()) return; KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); for (int i = 0; i < r.height(); ++i) { do { *dst->rawData() = abs(*dst->rawData() - *src->oldRawData()); } while (src->nextPixel() && dst->nextPixel()); dst->nextRow(); src->nextRow(); } m_d->outlineCacheValid &= selection->outlineCacheValid(); if (m_d->outlineCacheValid) { m_d->outlineCache = (m_d->outlineCache | selection->outlineCache()) - (m_d->outlineCache & selection->outlineCache()); } m_d->invalidateThumbnailImage(); } void KisPixelSelection::clear(const QRect & r) { if (*defaultPixel().data() != MIN_SELECTED) { KisFillPainter painter(KisPaintDeviceSP(this)); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); painter.fillRect(r, KoColor(Qt::white, cs), MIN_SELECTED); } else { KisPaintDevice::clear(r); } if (m_d->outlineCacheValid) { QPainterPath path; path.addRect(r); m_d->outlineCache -= path; } m_d->invalidateThumbnailImage(); } void KisPixelSelection::clear() { setDefaultPixel(KoColor(Qt::transparent, colorSpace())); KisPaintDevice::clear(); m_d->outlineCacheValid = true; m_d->outlineCache = QPainterPath(); // Empty the thumbnail image. It is a valid state. m_d->invalidateThumbnailImage(); m_d->thumbnailImageValid = true; } void KisPixelSelection::invert() { // Region is needed here (not exactBounds or extent), because // unselected but existing pixels need to be inverted too QRect rc = region().boundingRect(); if (!rc.isEmpty()) { KisSequentialIterator it(this, rc); while(it.nextPixel()) { *(it.rawData()) = MAX_SELECTED - *(it.rawData()); } } quint8 defPixel = MAX_SELECTED - *defaultPixel().data(); setDefaultPixel(KoColor(&defPixel, colorSpace())); if (m_d->outlineCacheValid) { QPainterPath path; path.addRect(defaultBounds()->bounds()); m_d->outlineCache = path - m_d->outlineCache; } m_d->invalidateThumbnailImage(); } void KisPixelSelection::moveTo(const QPoint &pt) { const int lod = defaultBounds()->currentLevelOfDetail(); const QPoint lod0Point = !lod ? pt : pt * KisLodTransform::lodToInvScale(lod); const QPoint offset = lod0Point - m_d->lod0CachesOffset; if (m_d->outlineCacheValid) { m_d->outlineCache.translate(offset); } if (m_d->thumbnailImageValid) { m_d->thumbnailImageTransform = QTransform::fromTranslate(offset.x(), offset.y()) * m_d->thumbnailImageTransform; } m_d->lod0CachesOffset = lod0Point; KisPaintDevice::moveTo(pt); } bool KisPixelSelection::isTotallyUnselected(const QRect & r) const { if (*defaultPixel().data() != MIN_SELECTED) return false; QRect sr = selectedExactRect(); return ! r.intersects(sr); } QRect KisPixelSelection::selectedRect() const { return extent(); } QRect KisPixelSelection::selectedExactRect() const { return exactBounds(); } QVector KisPixelSelection::outline() const { QRect selectionExtent = selectedExactRect(); /** * When the default pixel is not fully transparent, the * exactBounds() return extent of the device instead. To make this * value sane we should limit the calculated area by the bounds of * the image. */ if (*defaultPixel().data() != MIN_SELECTED) { selectionExtent &= defaultBounds()->bounds(); } qint32 xOffset = selectionExtent.x(); qint32 yOffset = selectionExtent.y(); qint32 width = selectionExtent.width(); qint32 height = selectionExtent.height(); KisOutlineGenerator generator(colorSpace(), MIN_SELECTED); // If the selection is small using a buffer is much faster try { quint8* buffer = new quint8[width*height]; readBytes(buffer, xOffset, yOffset, width, height); QVector paths = generator.outline(buffer, xOffset, yOffset, width, height); delete[] buffer; return paths; } catch(const std::bad_alloc&) { // Allocating so much memory failed, so we fall through to the slow option. warnKrita << "KisPixelSelection::outline ran out of memory allocating" << width << "*" << height << "bytes."; } return generator.outline(this, xOffset, yOffset, width, height); } bool KisPixelSelection::isEmpty() const { return *defaultPixel().data() == MIN_SELECTED && selectedRect().isEmpty(); } QPainterPath KisPixelSelection::outlineCache() const { QMutexLocker locker(&m_d->outlineCacheMutex); return m_d->outlineCache; } void KisPixelSelection::setOutlineCache(const QPainterPath &cache) { QMutexLocker locker(&m_d->outlineCacheMutex); m_d->outlineCache = cache; m_d->outlineCacheValid = true; m_d->thumbnailImageValid = false; } bool KisPixelSelection::outlineCacheValid() const { QMutexLocker locker(&m_d->outlineCacheMutex); return m_d->outlineCacheValid; } void KisPixelSelection::invalidateOutlineCache() { QMutexLocker locker(&m_d->outlineCacheMutex); m_d->outlineCacheValid = false; m_d->thumbnailImageValid = false; } void KisPixelSelection::recalculateOutlineCache() { QMutexLocker locker(&m_d->outlineCacheMutex); m_d->outlineCache = QPainterPath(); Q_FOREACH (const QPolygon &polygon, outline()) { m_d->outlineCache.addPolygon(polygon); /** * The outline generation algorithm has a small bug, which * results in the starting point be repeated twice in the * beginning of the path, instead of being put to the * end. Here we just explicitly close the path to workaround * it. * * \see KisSelectionTest::testOutlineGeneration() */ m_d->outlineCache.closeSubpath(); } m_d->outlineCacheValid = true; } bool KisPixelSelection::thumbnailImageValid() const { return m_d->thumbnailImageValid; } QImage KisPixelSelection::thumbnailImage() const { return m_d->thumbnailImage; } QTransform KisPixelSelection::thumbnailImageTransform() const { return m_d->thumbnailImageTransform; } QImage deviceToQImage(KisPaintDeviceSP device, const QRect &rc, const QColor &maskColor) { QImage image(rc.size(), QImage::Format_ARGB32); QColor color = maskColor; const qreal alphaScale = maskColor.alphaF(); KisSequentialConstIterator it(device, rc); while(it.nextPixel()) { quint8 value = (MAX_SELECTED - *(it.rawDataConst())) * alphaScale; color.setAlpha(value); QPoint pt(it.x(), it.y()); pt -= rc.topLeft(); image.setPixel(pt.x(), pt.y(), color.rgba()); } return image; } void KisPixelSelection::recalculateThumbnailImage(const QColor &maskColor) { QRect rc = selectedExactRect(); const int maxPreviewSize = 2000; if (rc.isEmpty()) { m_d->thumbnailImageTransform = QTransform(); m_d->thumbnailImage = QImage(); return; } if (rc.width() > maxPreviewSize || rc.height() > maxPreviewSize) { qreal factor = 1.0; if (rc.width() > rc.height()) { factor = qreal(maxPreviewSize) / rc.width(); } else { factor = qreal(maxPreviewSize) / rc.height(); } int newWidth = qRound(rc.width() * factor); int newHeight = qRound(rc.height() * factor); m_d->thumbnailImageTransform = QTransform::fromScale(qreal(rc.width()) / newWidth, qreal(rc.height()) / newHeight) * QTransform::fromTranslate(rc.x(), rc.y()); KisPaintDeviceSP thumbDevice = createThumbnailDevice(newWidth, newHeight, rc); QRect thumbRect(0, 0, newWidth, newHeight); m_d->thumbnailImage = deviceToQImage(thumbDevice, thumbRect, maskColor); } else { m_d->thumbnailImageTransform = QTransform::fromTranslate(rc.x(), rc.y()); m_d->thumbnailImage = deviceToQImage(this, rc, maskColor); } m_d->thumbnailImageValid = true; } void KisPixelSelection::setParentSelection(KisSelectionWSP selection) { m_d->parentSelection = selection; } KisSelectionWSP KisPixelSelection::parentSelection() const { return m_d->parentSelection; } void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection) { renderToProjection(projection, selectedExactRect()); } void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& rc) { QRect updateRect = rc & selectedExactRect(); if (updateRect.isValid()) { KisPainter::copyAreaOptimized(updateRect.topLeft(), KisPaintDeviceSP(this), projection, updateRect); } } diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp index b0a53c644b..d5437bf9c4 100644 --- a/libs/image/lazybrush/kis_colorize_mask.cpp +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -1,1176 +1,1176 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorize_mask.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_icon_utils.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_painter.h" #include "kis_fill_painter.h" #include "kis_lazy_fill_tools.h" #include "kis_cached_paint_device.h" #include "kis_paint_device_debug_utils.h" #include "kis_layer_properties_icons.h" #include "kis_thread_safe_signal_compressor.h" #include "kis_colorize_stroke_strategy.h" #include "kis_multiway_cut.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_macro_based_undo_store.h" #include "kis_post_execution_undo_adapter.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "krita_utils.h" using namespace KisLazyFillTools; struct KisColorizeMask::Private { Private(KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), fakePaintDevice(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), filteredSource(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), needAddCurrentKeyStroke(false), showKeyStrokes(true), showColoring(true), needsUpdate(true), originalSequenceNumber(-1), updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), updateIsRunning(false), filteringOptions(false, 4.0, 15, 0.7), limitToDeviceBounds(false) { } Private(const Private &rhs, KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(*rhs.coloringProjection)), fakePaintDevice(new KisPaintDevice(*rhs.fakePaintDevice)), filteredSource(new KisPaintDevice(*rhs.filteredSource)), filteredDeviceBounds(rhs.filteredDeviceBounds), needAddCurrentKeyStroke(rhs.needAddCurrentKeyStroke), showKeyStrokes(rhs.showKeyStrokes), showColoring(rhs.showColoring), needsUpdate(false), originalSequenceNumber(-1), updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), offset(rhs.offset), updateIsRunning(false), filteringOptions(rhs.filteringOptions), limitToDeviceBounds(rhs.limitToDeviceBounds) { Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) { keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent); } } KisColorizeMask *q = 0; QList keyStrokes; KisPaintDeviceSP coloringProjection; KisPaintDeviceSP fakePaintDevice; KisPaintDeviceSP filteredSource; QRect filteredDeviceBounds; KoColor currentColor; KisPaintDeviceSP currentKeyStrokeDevice; bool needAddCurrentKeyStroke; bool showKeyStrokes; bool showColoring; KisCachedSelection cachedSelection; KisCachedSelection cachedConversionSelection; bool needsUpdate; int originalSequenceNumber; KisThreadSafeSignalCompressor updateCompressor; KisThreadSafeSignalCompressor dirtyParentUpdateCompressor; KisThreadSafeSignalCompressor prefilterRecalculationCompressor; QPoint offset; bool updateIsRunning; QStack extentBeforeUpdateStart; FilteringOptions filteringOptions; bool filteringDirty = true; bool limitToDeviceBounds = false; bool filteredSourceValid(KisPaintDeviceSP parentDevice) { return !filteringDirty && originalSequenceNumber == parentDevice->sequenceNumber(); } void setNeedsUpdateImpl(bool value, bool requestedByUser); bool shouldShowFilteredSource() const; bool shouldShowColoring() const; }; KisColorizeMask::KisColorizeMask() : m_d(new Private(this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); connect(&m_d->prefilterRecalculationCompressor, SIGNAL(timeout()), SLOT(slotRecalculatePrefilteredImage())); m_d->updateCompressor.moveToThread(qApp->thread()); } KisColorizeMask::~KisColorizeMask() { } KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs) : KisEffectMask(rhs), m_d(new Private(*rhs.m_d, this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); m_d->updateCompressor.moveToThread(qApp->thread()); } void KisColorizeMask::initializeCompositeOp() { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer || !parentLayer->original()) return; KisImageSP image = parentLayer->image(); if (!image) return; const qreal samplePortion = 0.1; const qreal alphaPortion = KritaUtils::estimatePortionOfTransparentPixels(parentLayer->original(), image->bounds(), samplePortion); setCompositeOpId(alphaPortion > 0.3 ? COMPOSITE_BEHIND : COMPOSITE_MULT); } const KoColorSpace* KisColorizeMask::colorSpace() const { return m_d->fakePaintDevice->colorSpace(); } struct SetKeyStrokesColorSpaceCommand : public KUndo2Command { SetKeyStrokesColorSpaceCommand(const KoColorSpace *dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, QList *list, KisColorizeMaskSP node) : m_dstCS(dstCS), m_renderingIntent(renderingIntent), m_conversionFlags(conversionFlags), m_list(list), m_node(node) {} void undo() override { KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_oldColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_oldColors[i]; } m_node->setNeedsUpdate(true); } void redo() override { if (m_oldColors.isEmpty()) { Q_FOREACH(const KeyStroke &stroke, *m_list) { m_oldColors << stroke.color; m_newColors << stroke.color; m_newColors.last().convertTo(m_dstCS, m_renderingIntent, m_conversionFlags); } } KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_newColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_newColors[i]; } m_node->setNeedsUpdate(true); } private: QVector m_oldColors; QVector m_newColors; const KoColorSpace *m_dstCS; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setProfile(const KoColorProfile *profile) { // WARNING: there is no undo information, used only while loading! m_d->fakePaintDevice->setProfile(profile); m_d->coloringProjection->setProfile(profile); for (auto stroke : m_d->keyStrokes) { stroke.color.setProfile(profile); } } KUndo2Command* KisColorizeMask::setColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisCommandUtils; CompositeCommand *composite = new CompositeCommand(); - composite->addCommand(m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags)); - composite->addCommand(m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags)); + m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite); + m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite); KUndo2Command *strokesConversionCommand = new SetKeyStrokesColorSpaceCommand( dstColorSpace, renderingIntent, conversionFlags, &m_d->keyStrokes, KisColorizeMaskSP(this)); strokesConversionCommand->redo(); composite->addCommand(new SkipFirstRedoWrapper(strokesConversionCommand)); return composite; } bool KisColorizeMask::needsUpdate() const { return m_d->needsUpdate; } void KisColorizeMask::setNeedsUpdate(bool value) { m_d->setNeedsUpdateImpl(value, true); } void KisColorizeMask::Private::setNeedsUpdateImpl(bool value, bool requestedByUser) { if (value != needsUpdate) { needsUpdate = value; q->baseNodeChangedCallback(); if (!value && requestedByUser) { updateCompressor.start(); } } } void KisColorizeMask::slotUpdateRegenerateFilling(bool prefilterOnly) { KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); const bool filteredSourceValid = m_d->filteredSourceValid(src); m_d->originalSequenceNumber = src->sequenceNumber(); m_d->filteringDirty = false; if (!prefilterOnly) { m_d->coloringProjection->clear(); } KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return; KisImageSP image = parentLayer->image(); if (image) { m_d->updateIsRunning = true; QRect fillBounds; if (m_d->limitToDeviceBounds) { fillBounds |= src->exactBounds(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { fillBounds |= stroke.dev->exactBounds(); } fillBounds &= image->bounds(); } else { fillBounds = image->bounds(); } m_d->filteredDeviceBounds = fillBounds; KisColorizeStrokeStrategy *strategy = new KisColorizeStrokeStrategy(src, m_d->coloringProjection, m_d->filteredSource, filteredSourceValid, fillBounds, this, prefilterOnly); strategy->setFilteringOptions(m_d->filteringOptions); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const KoColor color = !stroke.isTransparent ? stroke.color : KoColor(Qt::transparent, stroke.color.colorSpace()); strategy->addKeyStroke(stroke.dev, color); } m_d->extentBeforeUpdateStart.push(extent()); connect(strategy, SIGNAL(sigFinished(bool)), SLOT(slotRegenerationFinished(bool))); connect(strategy, SIGNAL(sigCancelled()), SLOT(slotRegenerationCancelled())); KisStrokeId id = image->startStroke(strategy); image->endStroke(id); } } void KisColorizeMask::slotUpdateOnDirtyParent() { if (!parent()) { // When the colorize mask is being merged, // the update is performed for all the layers, // so the invisible areas around the canvas are included in the merged layer. // Colorize Mask gets the info that its parent is "dirty" (needs updating), // but when it arrives, the parent doesn't exists anymore and is set to null. // Colorize Mask doesn't work outside of the canvas anyway (at least in time of writing). return; } KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { const QRect &oldExtent = extent(); m_d->setNeedsUpdateImpl(true, false); m_d->filteringDirty = true; setDirty(oldExtent | extent()); } } void KisColorizeMask::slotRecalculatePrefilteredImage() { slotUpdateRegenerateFilling(true); } void KisColorizeMask::slotRegenerationFinished(bool prefilterOnly) { m_d->updateIsRunning = false; if (!prefilterOnly) { m_d->setNeedsUpdateImpl(false, false); } QRect oldExtent; if (!m_d->extentBeforeUpdateStart.isEmpty()) { oldExtent = m_d->extentBeforeUpdateStart.pop(); } else { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->extentBeforeUpdateStart.isEmpty()); // always fail! } setDirty(oldExtent | extent()); } void KisColorizeMask::slotRegenerationCancelled() { slotRegenerationFinished(true); } KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisMask::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeEditKeyStrokes, showKeyStrokes()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring()); return l; } void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisMask::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) { if (m_d->needsUpdate && m_d->needsUpdate != property.state.toBool()) { setNeedsUpdate(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeEditKeyStrokes.id()) { if (m_d->showKeyStrokes != property.state.toBool()) { setShowKeyStrokes(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) { if (m_d->showColoring != property.state.toBool()) { setShowColoring(property.state.toBool()); } } } } KisPaintDeviceSP KisColorizeMask::paintDevice() const { return m_d->showKeyStrokes && !m_d->updateIsRunning ? m_d->fakePaintDevice : KisPaintDeviceSP(); } KisPaintDeviceSP KisColorizeMask::coloringProjection() const { return m_d->coloringProjection; } KisPaintDeviceSP KisColorizeMask::colorPickSourceDevice() const { return m_d->shouldShowColoring() && !m_d->coloringProjection->extent().isEmpty() ? m_d->coloringProjection : projection(); } QIcon KisColorizeMask::icon() const { return KisIconUtils::loadIcon("colorizeMask"); } bool KisColorizeMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } bool KisColorizeMask::Private::shouldShowFilteredSource() const { return !updateIsRunning && showKeyStrokes && !filteringDirty && filteredSource && !filteredSource->extent().isEmpty(); } bool KisColorizeMask::Private::shouldShowColoring() const { return !updateIsRunning && showColoring && coloringProjection; } QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect &rect, PositionToFilthy maskPos) const { Q_UNUSED(maskPos); if (maskPos == N_ABOVE_FILTHY) { // the source layer has changed, we should update the filtered cache! if (!m_d->filteringDirty) { emit sigUpdateOnDirtyParent(); } } KIS_ASSERT(dst != src); // Draw the filling and the original layer { KisPainter gc(dst); if (m_d->shouldShowFilteredSource()) { const QRect drawRect = m_d->limitToDeviceBounds ? rect & m_d->filteredDeviceBounds : rect; gc.setOpacity(128); gc.bitBlt(drawRect.topLeft(), m_d->filteredSource, drawRect); } else { gc.setOpacity(255); gc.bitBlt(rect.topLeft(), src, rect); } if (m_d->shouldShowColoring()) { gc.setOpacity(opacity()); gc.setCompositeOp(compositeOpId()); gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect); } } // Draw the key strokes if (m_d->showKeyStrokes) { KisIndirectPaintingSupport::ReadLocker locker(this); KisSelectionSP selection = m_d->cachedSelection.getSelection(); KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisFillPainter gc(dst); QList extendedStrokes = m_d->keyStrokes; if (m_d->currentKeyStrokeDevice && m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { extendedStrokes << KeyStroke(m_d->currentKeyStrokeDevice, m_d->currentColor); } Q_FOREACH (const KeyStroke &stroke, extendedStrokes) { selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); if (stroke.color == m_d->currentColor || (isTemporaryTargetErasing && temporaryExtent.intersects(selection->pixelSelection()->selectedRect()))) { if (temporaryTarget) { tempSelection->copyAlphaFrom(temporaryTarget, rect); KisPainter selectionPainter(selection->pixelSelection()); setupTemporaryPainter(&selectionPainter); selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect); } } gc.fillSelection(rect, stroke.color); } m_d->cachedSelection.putSelection(selection); m_d->cachedSelection.putSelection(conversionSelection); } return rect; } struct DeviceExtentPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->extent(); } }; struct DeviceExactBoundsPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->exactBounds(); } }; template QRect KisColorizeMask::calculateMaskBounds(DeviceMetricPolicy boundsPolicy) const { QRect rc; if (m_d->shouldShowFilteredSource()) { rc |= boundsPolicy(m_d->filteredSource); } if (m_d->shouldShowColoring()) { rc |= boundsPolicy(m_d->coloringProjection); } if (m_d->showKeyStrokes) { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { rc |= boundsPolicy(stroke.dev); } KisIndirectPaintingSupport::ReadLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { rc |= boundsPolicy(temporaryTarget); } } return rc; } QRect KisColorizeMask::extent() const { return calculateMaskBounds(DeviceExtentPolicy()); } QRect KisColorizeMask::exactBounds() const { return calculateMaskBounds(DeviceExactBoundsPolicy()); } QRect KisColorizeMask::nonDependentExtent() const { return extent(); } KisImageSP KisColorizeMask::fetchImage() const { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return KisImageSP(); return parentLayer->image(); } void KisColorizeMask::setImage(KisImageWSP image) { KisDefaultBoundsSP bounds(new KisDefaultBounds(image)); auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { it->dev->setDefaultBounds(bounds); } m_d->coloringProjection->setDefaultBounds(bounds); m_d->fakePaintDevice->setDefaultBounds(bounds); m_d->filteredSource->setDefaultBounds(bounds); } void KisColorizeMask::setCurrentColor(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); WriteLocker locker(this); m_d->setNeedsUpdateImpl(true, false); QList::const_iterator it = std::find_if(m_d->keyStrokes.constBegin(), m_d->keyStrokes.constEnd(), [color] (const KeyStroke &s) { return s.color == color; }); KisPaintDeviceSP activeDevice; bool newKeyStroke = false; if (it == m_d->keyStrokes.constEnd()) { activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); activeDevice->setParentNode(this); activeDevice->setDefaultBounds(KisDefaultBoundsBaseSP(new KisDefaultBounds(fetchImage()))); newKeyStroke = true; } else { activeDevice = it->dev; } m_d->currentColor = color; m_d->currentKeyStrokeDevice = activeDevice; m_d->needAddCurrentKeyStroke = newKeyStroke; } struct KeyStrokeAddRemoveCommand : public KisCommandUtils::FlipFlopCommand { KeyStrokeAddRemoveCommand(bool add, int index, KeyStroke stroke, QList *list, KisColorizeMaskSP node) : FlipFlopCommand(!add), m_index(index), m_stroke(stroke), m_list(list), m_node(node) {} void partA() override { m_list->insert(m_index, m_stroke); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } void partB() override { KIS_ASSERT_RECOVER_RETURN((*m_list)[m_index] == m_stroke); m_list->removeAt(m_index); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } private: int m_index; KeyStroke m_stroke; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) { Q_UNUSED(layer); WriteLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisSavedMacroCommand *macro = undoAdapter->createMacro(transactionText); KisMacroBasedUndoStore store(macro); KisPostExecutionUndoAdapter fakeUndoAdapter(&store, undoAdapter->strokesFacade()); /** * Add a new key stroke plane */ if (m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { KeyStroke key(m_d->currentKeyStrokeDevice, m_d->currentColor); KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( true, m_d->keyStrokes.size(), key, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } /** * When erasing, the brush affects all the key strokes, not only * the current one. */ if (!isTemporaryTargetErasing) { mergeToLayerImpl(m_d->currentKeyStrokeDevice, &fakeUndoAdapter, transactionText, timedID, false); } else { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { if (temporaryExtent.intersects(stroke.dev->extent())) { mergeToLayerImpl(stroke.dev, &fakeUndoAdapter, transactionText, timedID, false); } } } mergeToLayerImpl(m_d->fakePaintDevice, &fakeUndoAdapter, transactionText, timedID, false); m_d->currentKeyStrokeDevice = 0; m_d->currentColor = KoColor(); releaseResources(); /** * Try removing the key strokes that has been completely erased */ if (isTemporaryTargetErasing) { for (int index = 0; index < m_d->keyStrokes.size(); /*noop*/) { const KeyStroke &stroke = m_d->keyStrokes[index]; if (stroke.dev->exactBounds().isEmpty()) { KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( false, index, stroke, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } else { index++; } } } undoAdapter->addMacro(macro); } void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const bool nonAlphaDst = !(*painter->device()->colorSpace() == *alpha8); if (nonAlphaDst) { Q_FOREACH (const QRect &rc, src->region().rects()) { painter->bitBlt(rc.topLeft(), src, rc); } } else { KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); Q_FOREACH (const QRect &rc, src->region().rects()) { tempSelection->copyAlphaFrom(src, rc); painter->bitBlt(rc.topLeft(), tempSelection, rc); } m_d->cachedSelection.putSelection(conversionSelection); } } bool KisColorizeMask::supportsNonIndirectPainting() const { return false; } bool KisColorizeMask::showColoring() const { return m_d->showColoring; } void KisColorizeMask::setShowColoring(bool value) { QRect savedExtent; if (m_d->showColoring && !value) { savedExtent = extent(); } m_d->showColoring = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } } bool KisColorizeMask::showKeyStrokes() const { return m_d->showKeyStrokes; } void KisColorizeMask::setShowKeyStrokes(bool value) { QRect savedExtent; if (m_d->showKeyStrokes && !value) { savedExtent = extent(); } m_d->showKeyStrokes = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } regeneratePrefilteredDeviceIfNeeded(); } KisColorizeMask::KeyStrokeColors KisColorizeMask::keyStrokesColors() const { KeyStrokeColors colors; // TODO: thread safety! for (int i = 0; i < m_d->keyStrokes.size(); i++) { colors.colors << m_d->keyStrokes[i].color; if (m_d->keyStrokes[i].isTransparent) { colors.transparentIndex = i; } } return colors; } struct SetKeyStrokeColorsCommand : public KUndo2Command { SetKeyStrokeColorsCommand(const QList newList, QList *list, KisColorizeMaskSP node) : m_newList(newList), m_oldList(*list), m_list(list), m_node(node) {} void redo() override { *m_list = m_newList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } void undo() override { *m_list = m_oldList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } private: QList m_newList; QList m_oldList; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setKeyStrokesColors(KeyStrokeColors colors) { KIS_ASSERT_RECOVER_RETURN(colors.colors.size() == m_d->keyStrokes.size()); QList newList = m_d->keyStrokes; for (int i = 0; i < newList.size(); i++) { newList[i].color = colors.colors[i]; newList[i].color.convertTo(colorSpace()); newList[i].isTransparent = colors.transparentIndex == i; } KisProcessingApplicator applicator(fetchImage(), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Change Key Stroke Color")); applicator.applyCommand( new SetKeyStrokeColorsCommand( newList, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } void KisColorizeMask::removeKeyStroke(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); QList::iterator it = std::find_if(m_d->keyStrokes.begin(), m_d->keyStrokes.end(), [color] (const KeyStroke &s) { return s.color == color; }); KIS_SAFE_ASSERT_RECOVER_RETURN(it != m_d->keyStrokes.end()); const int index = it - m_d->keyStrokes.begin(); KisProcessingApplicator applicator(KisImageWSP(fetchImage()), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Remove Key Stroke")); applicator.applyCommand( new KeyStrokeAddRemoveCommand( false, index, *it, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } QVector KisColorizeMask::allPaintDevices() const { QVector devices; Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { devices << stroke.dev; } devices << m_d->coloringProjection; devices << m_d->fakePaintDevice; return devices; } void KisColorizeMask::resetCache() { m_d->filteredSource->clear(); m_d->originalSequenceNumber = -1; m_d->filteringDirty = true; rerenderFakePaintDevice(); slotUpdateRegenerateFilling(true); } void KisColorizeMask::setUseEdgeDetection(bool value) { m_d->filteringOptions.useEdgeDetection = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::useEdgeDetection() const { return m_d->filteringOptions.useEdgeDetection; } void KisColorizeMask::setEdgeDetectionSize(qreal value) { m_d->filteringOptions.edgeDetectionSize = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::edgeDetectionSize() const { return m_d->filteringOptions.edgeDetectionSize; } void KisColorizeMask::setFuzzyRadius(qreal value) { m_d->filteringOptions.fuzzyRadius = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::fuzzyRadius() const { return m_d->filteringOptions.fuzzyRadius; } void KisColorizeMask::setCleanUpAmount(qreal value) { m_d->filteringOptions.cleanUpAmount = value; setNeedsUpdate(true); } qreal KisColorizeMask::cleanUpAmount() const { return m_d->filteringOptions.cleanUpAmount; } void KisColorizeMask::setLimitToDeviceBounds(bool value) { m_d->limitToDeviceBounds = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::limitToDeviceBounds() const { return m_d->limitToDeviceBounds; } void KisColorizeMask::rerenderFakePaintDevice() { m_d->fakePaintDevice->clear(); KisFillPainter gc(m_d->fakePaintDevice); KisSelectionSP selection = m_d->cachedSelection.getSelection(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const QRect rect = stroke.dev->extent(); selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); gc.fillSelection(rect, stroke.color); } m_d->cachedSelection.putSelection(selection); } void KisColorizeMask::testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent) { m_d->keyStrokes << KeyStroke(dev, color, isTransparent); } void KisColorizeMask::testingRegenerateMask() { slotUpdateRegenerateFilling(); m_d->updateIsRunning = false; } KisPaintDeviceSP KisColorizeMask::testingFilteredSource() const { return m_d->filteredSource; } QList KisColorizeMask::fetchKeyStrokesDirect() const { return m_d->keyStrokes; } void KisColorizeMask::setKeyStrokesDirect(const QList &strokes) { m_d->keyStrokes = strokes; for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end(); ++it) { it->dev->setParentNode(this); } KisImageSP image = fetchImage(); KIS_SAFE_ASSERT_RECOVER_RETURN(image); setImage(image); } qint32 KisColorizeMask::x() const { return m_d->offset.x(); } qint32 KisColorizeMask::y() const { return m_d->offset.y(); } void KisColorizeMask::setX(qint32 x) { const QPoint oldOffset = m_d->offset; m_d->offset.rx() = x; moveAllInternalDevices(m_d->offset - oldOffset); } void KisColorizeMask::setY(qint32 y) { const QPoint oldOffset = m_d->offset; m_d->offset.ry() = y; moveAllInternalDevices(m_d->offset - oldOffset); } KisPaintDeviceList KisColorizeMask::getLodCapableDevices() const { KisPaintDeviceList list; auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { list << it->dev; } list << m_d->coloringProjection; list << m_d->fakePaintDevice; list << m_d->filteredSource; return list; } void KisColorizeMask::regeneratePrefilteredDeviceIfNeeded() { if (!parent()) return; KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { // update the prefiltered source if needed slotUpdateRegenerateFilling(true); } } void KisColorizeMask::moveAllInternalDevices(const QPoint &diff) { QVector devices = allPaintDevices(); Q_FOREACH (KisPaintDeviceSP dev, devices) { dev->moveTo(dev->offset() + diff); } } diff --git a/libs/image/tests/kis_cs_conversion_test.cpp b/libs/image/tests/kis_cs_conversion_test.cpp index 08c9a3a846..cf056ecb09 100644 --- a/libs/image/tests/kis_cs_conversion_test.cpp +++ b/libs/image/tests/kis_cs_conversion_test.cpp @@ -1,104 +1,104 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cs_conversion_test.h" #include #include #include #include #include "kis_painter.h" #include "kis_types.h" #include "kis_paint_device.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_datamanager.h" #include "kis_global.h" #include "testutil.h" #include "kis_transaction.h" #include "kis_image.h" #include "testing_timed_default_bounds.h" void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs) { QString profile1("no profile"); QString profile2("no profile"); if (srcCs->profile()) profile1 = srcCs->profile()->name(); if (dstCs->profile()) profile2 = dstCs->profile()->name(); QWARN(QString("Failed %1 %2 -> %3 %4 %5") .arg(srcCs->name()) .arg(profile1) .arg(dstCs->name()) .arg(profile2) .arg(reason) .toLatin1()); } void KisCsConversionTest::testColorSpaceConversion() { QTime t; t.start(); QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); int failedColorSpaces = 0; QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); Q_FOREACH (const KoColorSpace * srcCs, colorSpaces) { Q_FOREACH (const KoColorSpace * dstCs, colorSpaces) { KisPaintDeviceSP dev = new KisPaintDevice(srcCs); dev->convertFromQImage(image, 0); dev->moveTo(10, 10); // Unalign with tile boundaries dev->setDefaultBounds(new TestUtil::TestingTimedDefaultBounds(dev->exactBounds())); - delete dev->convertTo(dstCs); + dev->convertTo(dstCs); if (dev->exactBounds() != QRect(10, 10, image.width(), image.height())) { logFailure("bounds", srcCs, dstCs); failedColorSpaces++; } if (dev->pixelSize() != dstCs->pixelSize()) { logFailure("pixelsize", srcCs, dstCs); failedColorSpaces++; } if (*dev->colorSpace() != *dstCs) { logFailure("dest cs", srcCs, dstCs); failedColorSpaces++; } } } qDebug() << colorSpaces.size() * colorSpaces.size() << "conversions" << " done in " << t.elapsed() << "ms"; if (failedColorSpaces > 0) { QFAIL(QString("Failed conversions %1, see log for details.").arg(failedColorSpaces).toLatin1()); } } KISTEST_MAIN(KisCsConversionTest) diff --git a/libs/image/tests/kis_filter_weights_applicator_test.cpp b/libs/image/tests/kis_filter_weights_applicator_test.cpp index a31420eeca..75f6025460 100644 --- a/libs/image/tests/kis_filter_weights_applicator_test.cpp +++ b/libs/image/tests/kis_filter_weights_applicator_test.cpp @@ -1,648 +1,648 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_filter_weights_applicator_test.h" #include #include #include #include #include "kis_paint_device.h" //#define DEBUG_ENABLED #include "kis_filter_weights_applicator.h" void debugSpan(const KisFilterWeightsApplicator::BlendSpan &span) { dbgKrita << ppVar(span.weights->centerIndex); for (int i = 0; i < span.weights->span; i++) { dbgKrita << "Weights" << i << span.weights->weight[i]; } dbgKrita << ppVar(span.firstBlendPixel); dbgKrita << ppVar(span.offset); dbgKrita << ppVar(span.offsetInc); } void testSpan(qreal scale, qreal dx, int dst_l, int expectedFirstPixel, qreal expectedOffset, qreal expectedOffsetInc) { KisFilterStrategy *filter = new KisBilinearFilterStrategy(); KisFilterWeightsBuffer buf(filter, qAbs(scale)); KisFilterWeightsApplicator applicator(0, 0, scale, 0.0, dx, false); KisFilterWeightsApplicator::BlendSpan span; span = applicator.calculateBlendSpan(dst_l, 0, &buf); //debugSpan(span); if (span.firstBlendPixel != expectedFirstPixel || span.offset != KisFixedPoint(expectedOffset) || span.offsetInc != KisFixedPoint(expectedOffsetInc)) { dbgKrita << "Failed to generate a span:"; dbgKrita << ppVar(scale) << ppVar(dx) << ppVar(dst_l); dbgKrita << ppVar(span.firstBlendPixel) << ppVar(expectedFirstPixel); dbgKrita << ppVar(span.offset) << ppVar(KisFixedPoint(expectedOffset)); dbgKrita << ppVar(span.offsetInc) << ppVar(KisFixedPoint(expectedOffsetInc)); QFAIL("fail"); } } void KisFilterWeightsApplicatorTest::testSpan_Scale_2_0_Aligned() { testSpan(2.0, 0.0, 0, -1, 0.25, 1.0); testSpan(2.0, 0.0, 1, 0, 0.75, 1.0); testSpan(2.0, 0.0, -1, -1, 0.75, 1.0); testSpan(2.0, 0.0, -2, -2, 0.25, 1.0); } void KisFilterWeightsApplicatorTest::testSpan_Scale_2_0_Shift_0_5() { testSpan(2.0, 0.5, 0, -1, 0.5, 1.0); testSpan(2.0, 0.5, 1, -1, 0.0, 1.0); testSpan(2.0, 0.5, -1, -2, 0.0, 1.0); testSpan(2.0, 0.5, -2, -2, 0.5, 1.0); } void KisFilterWeightsApplicatorTest::testSpan_Scale_2_0_Shift_0_75() { testSpan(2.0, 0.75, 0, -1, 0.625, 1.0); testSpan(2.0, 0.75, 1, -1, 0.125, 1.0); testSpan(2.0, 0.75, -1, -2, 0.125, 1.0); testSpan(2.0, 0.75, -2, -2, 0.625, 1.0); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Aligned() { testSpan(0.5, 0.0, 0, -1, 0.25, 0.5); testSpan(0.5, 0.0, 1, 1, 0.25, 0.5); testSpan(0.5, 0.0, -1, -3, 0.25, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_5() { testSpan(0.5, 0.5, 0, -2, 0.25, 0.5); testSpan(0.5, 0.5, 1, 0, 0.25, 0.5); testSpan(0.5, 0.5, -1, -4, 0.25, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_25() { testSpan(0.5, 0.25, 0, -2, 0.0, 0.5); testSpan(0.5, 0.25, 1, 0, 0.0, 0.5); testSpan(0.5, 0.25, -1, -4, 0.0, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_375() { testSpan(0.5, 0.375, 0, -2, 0.125, 0.5); testSpan(0.5, 0.375, 1, 0, 0.125, 0.5); testSpan(0.5, 0.375, -1, -4, 0.125, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_m0_5() { testSpan(0.5, -0.5, 0, 0, 0.25, 0.5); testSpan(0.5, -0.5, 1, 2, 0.25, 0.5); testSpan(0.5, -0.5, -1, -2, 0.25, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_m0_25() { testSpan(0.5, -0.25, 0, -1, 0.0, 0.5); testSpan(0.5, -0.25, 1, 1, 0.0, 0.5); testSpan(0.5, -0.25, -1, -3, 0.0, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_m0_375() { testSpan(0.5, -0.375, 0, 0, 0.375, 0.5); testSpan(0.5, -0.375, 1, 2, 0.375, 0.5); testSpan(0.5, -0.375, -1, -2, 0.375, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_1_0_Aligned_Mirrored() { testSpan(-1.0, 0.0, 0, -2, 0.0, 1.0); testSpan(-1.0, 0.0, 1, -3, 0.0, 1.0); testSpan(-1.0, 0.0, -1, -1, 0.0, 1.0); testSpan(-1.0, 0.0, -2, 0, 0.0, 1.0); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Aligned_Mirrored() { testSpan(-0.5, 0.0, 0, -3, 0.25, 0.5); testSpan(-0.5, 0.0, 1, -5, 0.25, 0.5); testSpan(-0.5, 0.0, -1, -1, 0.25, 0.5); testSpan(-0.5, 0.0, -2, 1, 0.25, 0.5); } void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_125_Mirrored() { testSpan(-0.5, 0.125, 0, -3, 0.125, 0.5); testSpan(-0.5, 0.125, 1, -5, 0.125, 0.5); testSpan(-0.5, 0.125, -1, -1, 0.125, 0.5); testSpan(-0.5, 0.125, -2, 1, 0.125, 0.5); } void printPixels(KisPaintDeviceSP dev, int x0, int len, bool horizontal) { for (int i = x0; i < x0 + len; i++) { QColor c; int x = horizontal ? i : 0; int y = horizontal ? 0 : i; dev->pixel(x, y, &c); dbgKrita << "px" << x << y << "|" << c.red() << c.green() << c.blue() << c.alpha(); } } void checkRA(KisPaintDeviceSP dev, int x0, int len, quint8 r[], quint8 a[], bool horizontal) { for (int i = 0; i < len; i++) { QColor c; int x = horizontal ? x0 + i : 0; int y = horizontal ? 0 : x0 + i; dev->pixel(x, y, &c); if (c.red() != r[i] || c.alpha() != a[i]) { dbgKrita << "Failed to compare RA channels:" << ppVar(x0 + i); dbgKrita << "Red:" << c.red() << "Expected:" << r[i]; dbgKrita << "Alpha:" << c.alpha() << "Expected:" << a[i]; QFAIL("failed"); } } } void testLineImpl(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge, bool horizontal, KisFilterStrategy *filter = 0, KisPaintDeviceSP dev = 0) { int startPos = 0; int endPos = 4; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); if (!filter) { filter = new KisBilinearFilterStrategy(); } if (!dev) { dev = new KisPaintDevice(cs); for (int i = 0; i < 4; i++) { int x = horizontal ? i : 0; int y = horizontal ? 0 : i; dev->setPixel(x,y,QColor(10 + i * 10, 20 + i * 10, 40 + i * 10)); } { quint8 r[] = { 0, 10, 20, 30, 40, 0, 0}; quint8 a[] = { 0,255,255,255,255, 0, 0}; checkRA(dev, -1, 6, r, a, horizontal); } startPos = 0; endPos = 4; } else { QRect rc = dev->exactBounds(); if (horizontal) { startPos = rc.left(); endPos = rc.left() + rc.width(); } else { startPos = rc.top(); endPos = rc.top() + rc.height(); } } KisFilterWeightsBuffer buf(filter, qAbs(scale)); KisFilterWeightsApplicator applicator(dev, dev, scale, 0.0, dx, clampToEdge); KisFilterWeightsApplicator::LinePos srcPos(startPos, endPos); KisFilterWeightsApplicator::LinePos dstPos; if (horizontal) { dstPos = applicator.processLine(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat())); } else { dstPos = applicator.processLine(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat())); } QRect rc = dev->exactBounds(); if (horizontal) { QVERIFY(rc.left() >= dstPos.start()); QVERIFY(rc.left() + rc.width() <= dstPos.end()); } else { QVERIFY(rc.top() >= dstPos.start()); QVERIFY(rc.top() + rc.height() <= dstPos.end()); } //printPixels(dev, x0, len, horizontal); checkRA(dev, x0, len, expR, expA, horizontal); } void testLine(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge = false, KisFilterStrategy* filter = 0, KisPaintDeviceSP dev = 0) { testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, true, filter, dev); testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, false, filter, dev); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned() { qreal scale = 1.0; qreal dx = 0.0; quint8 r[] = { 0, 10, 20, 30, 40, 0, 0}; quint8 a[] = { 0,255,255,255,255, 0, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_0_5() { qreal scale = 1.0; qreal dx = 0.5; quint8 r[] = { 0, 10, 15, 25, 35, 40, 0}; quint8 a[] = { 0,128,255,255,255,127, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_m0_5() { qreal scale = 1.0; qreal dx = -0.5; quint8 r[] = { 10, 15, 25, 35, 40, 0, 0}; quint8 a[] = {128,255,255,255,127, 0, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_0_25() { qreal scale = 1.0; qreal dx = 0.25; quint8 r[] = { 0, 10, 17, 27, 37, 40, 0}; quint8 a[] = { 0,191,255,255,255, 64, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_m0_25() { qreal scale = 1.0; qreal dx = -0.25; quint8 r[] = { 10, 12, 22, 32, 40, 0, 0}; quint8 a[] = { 64,255,255,255,191, 0, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Aligned() { qreal scale = 0.5; qreal dx = 0.0; quint8 r[] = { 10, 17, 32, 40, 0, 0, 0}; quint8 a[] = { 32,223,223, 32, 0, 0, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Shift_0_25() { qreal scale = 0.5; qreal dx = 0.25; quint8 r[] = { 0, 13, 30, 40, 0, 0, 0}; quint8 a[] = { 0,191,255, 64, 0, 0, 0}; testLine(scale, dx, r, a, -1, 7); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Aligned() { qreal scale = 2.0; qreal dx = 0.0; quint8 r[] = { 0, 10, 10, 12, 17, 22, 27, 32, 37, 40, 40, 0}; quint8 a[] = { 0, 64,191,255,255,255,255,255,255,191, 64, 0}; testLine(scale, dx, r, a, -2, 12); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Shift_0_25() { qreal scale = 2.0; qreal dx = 0.25; quint8 r[] = { 0, 10, 10, 11, 16, 21, 26, 31, 36, 40, 40, 0}; quint8 a[] = { 0, 32,159,255,255,255,255,255,255,223, 96, 0}; testLine(scale, dx, r, a, -2, 12); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Shift_0_5() { qreal scale = 2.0; qreal dx = 0.5; quint8 r[] = { 0, 0, 10, 10, 15, 20, 25, 30, 35, 40, 40, 0}; quint8 a[] = { 0, 0,128,255,255,255,255,255,255,255,127, 0}; testLine(scale, dx, r, a, -2, 12); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned_Clamped() { qreal scale = 1.0; qreal dx = 0.0; quint8 r[] = { 0, 10, 20, 30, 40, 0, 0}; quint8 a[] = { 0,255,255,255,255, 0, 0}; testLine(scale, dx, r, a, -1, 7, true); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Aligned_Clamped() { qreal scale = 0.5; qreal dx = 0.0; quint8 r[] = { 0, 16, 33, 0, 0, 0, 0}; quint8 a[] = { 0,255,255, 0, 0, 0, 0}; testLine(scale, dx, r, a, -1, 7, true); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Aligned_Clamped() { qreal scale = 2.0; qreal dx = 0.0; quint8 r[] = { 0, 0, 10, 12, 17, 22, 27, 32, 37, 40, 0, 0}; quint8 a[] = { 0, 0,255,255,255,255,255,255,255,255, 0, 0}; testLine(scale, dx, r, a, -2, 12, true); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned_Mirrored() { qreal scale = -1.0; qreal dx = 0.0; quint8 r[] = { 0, 0, 40, 30, 20, 10, 0, 0, 0, 0}; quint8 a[] = { 0, 0,255,255,255,255, 0, 0, 0, 0}; testLine(scale, dx, r, a, -6, 10); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_0_25_Mirrored() { qreal scale = -1.0; qreal dx = 0.25; quint8 r[] = { 0, 0, 40, 32, 22, 12, 10, 0, 0, 0}; quint8 a[] = { 0, 0,191,255,255,255, 64, 0, 0, 0}; testLine(scale, dx, r, a, -6, 10); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Aligned_Mirrored_Clamped() { qreal scale = -0.5; qreal dx = 0.0; quint8 r[] = { 0, 0, 0, 0, 33, 16, 0, 0, 0, 0}; quint8 a[] = { 0, 0, 0, 0,255,255, 0, 0, 0, 0}; testLine(scale, dx, r, a, -6, 10, true); } void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Shift_0_125_Mirrored() { qreal scale = -0.5; qreal dx = 0.125; quint8 r[] = { 0, 0, 0, 40, 34, 18, 10, 0, 0, 0}; quint8 a[] = { 0, 0, 0, 16,207,239, 48, 0, 0, 0}; testLine(scale, dx, r, a, -6, 10); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_2x() { qreal scale = 2.0; qreal dx = 0; quint8 r[] = {0, 10, 10, 20, 20, 30, 30, 40, 40, 0, 0}; quint8 a[] = {0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 11, true, filter); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_1x() { qreal scale = 1.0; qreal dx = 0; quint8 r[] = { 0, 10, 20, 30, 40, 0, 0}; quint8 a[] = { 0,255,255,255,255, 0, 0}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 7, false, filter); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_05x() { qreal scale = 0.5; qreal dx = 0; quint8 r[] = { 0, 10, 30, 0, 0, 0, 0}; quint8 a[] = { 0,255,255, 0, 0, 0, 0}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 7, false, filter); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_077x() { qreal scale = 0.77; qreal dx = 0; quint8 r[] = { 0, 10, 20, 40, 0, 0, 0}; quint8 a[] = { 0,255,255, 255, 0, 0, 0}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 7, false, filter); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_074x() { qreal scale = 0.74; qreal dx = 0; quint8 r[] = { 0, 10, 30, 40, 0, 0, 0}; quint8 a[] = { 0,255,255, 255, 0, 0, 0}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 7, false, filter); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_075x() { qreal scale = 0.75; qreal dx = 0; quint8 r[] = { 0, 10, 20, 40, 0, 0, 0}; quint8 a[] = { 0,255,255, 255, 0, 0, 0}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 7, false, filter); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_15x() { qreal scale = 1.5; qreal dx = 0; quint8 r[] = { 0, 10, 10, 20, 30, 30, 40}; quint8 a[] = { 0,255,255, 255, 255, 255, 255}; KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLine(scale, dx, r, a, -1, 7, false, filter); } KisPaintDeviceSP prepareUniformPaintDevice(int pixelsNumber, bool horizontal) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); for (int i = 0; i < pixelsNumber; i++) { int x = horizontal ? i : 0; int y = horizontal ? 0 : i; QColor c = QColor(10, 0, 0, 255); dev->setPixel(x, y, c); } return dev; } -void prepareUniformPixels(quint8 r[], quint8 a[], int pixelsNumber, bool horizontal) +void prepareUniformPixels(quint8 r[], quint8 a[], int pixelsNumber, bool /*horizontal*/) { for (int i = 0; i < pixelsNumber; i++) { QColor c = QColor(10, 0, 0, 255); r[i] = c.red(); a[i] = c.alpha(); } } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_0098x_horizontal() { int before = 5075; int after = 500; qreal scale = before/after; qreal dx = 0; bool horizontal = true; KisPaintDeviceSP dev = prepareUniformPaintDevice(before, horizontal); quint8 *r = new quint8[after]; quint8 *a = new quint8[after]; prepareUniformPixels(r, a, after, horizontal); KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLineImpl(scale, dx, r, a, 0, after, false, horizontal, filter, dev); } void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_0098x_vertical() { int before = 4725; int after = 466; qreal scale = before/after; qreal dx = 0; bool horizontal = false; KisPaintDeviceSP dev = prepareUniformPaintDevice(before, horizontal); quint8 *r = new quint8[after]; quint8 *a = new quint8[after]; prepareUniformPixels(r, a, after, horizontal); KisFilterStrategy* filter = new KisBoxFilterStrategy(); testLineImpl(scale, dx, r, a, 0, after, false, horizontal, filter, dev); } void KisFilterWeightsApplicatorTest::benchmarkProcesssLine() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KisFilterStrategy *filter = new KisBilinearFilterStrategy(); const qreal scale = 0.873; const qreal dx = 0.0387; KisFilterWeightsBuffer buf(filter, qAbs(scale)); KisFilterWeightsApplicator applicator(dev, dev, scale, 0.0, dx, false); for (int i = 0; i < 32767; i++) { dev->setPixel(i,0,QColor(10 + i%240,20,40)); } KisFilterWeightsApplicator::LinePos linePos(0,32767); QBENCHMARK { applicator.processLine(linePos,0,&buf, filter->support(buf.weightsPositionScale().toFloat())); } } QTEST_MAIN(KisFilterWeightsApplicatorTest) diff --git a/libs/image/tests/kis_paint_device_test.cpp b/libs/image/tests/kis_paint_device_test.cpp index e0c084f250..acf0149928 100644 --- a/libs/image/tests/kis_paint_device_test.cpp +++ b/libs/image/tests/kis_paint_device_test.cpp @@ -1,2367 +1,2371 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device_test.h" #include #include #include #include #include #include #include "kis_paint_device_writer.h" #include "kis_painter.h" #include "kis_types.h" #include "kis_paint_device.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_datamanager.h" #include "kis_global.h" #include "testutil.h" #include "kis_transaction.h" #include "kis_image.h" #include "config-limit-long-tests.h" #include "kistest.h" class KisFakePaintDeviceWriter : public KisPaintDeviceWriter { public: KisFakePaintDeviceWriter(KoStore *store) : m_store(store) { } bool write(const QByteArray &data) override { return (m_store->write(data) == data.size()); } bool write(const char* data, qint64 length) override { return (m_store->write(data, length) == length); } KoStore *m_store; }; void KisPaintDeviceTest::testCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->objectName().isEmpty()); dev = new KisPaintDevice(cs); QVERIFY(*dev->colorSpace() == *cs); QVERIFY(dev->x() == 0); QVERIFY(dev->y() == 0); QVERIFY(dev->pixelSize() == cs->pixelSize()); QVERIFY(dev->channelCount() == cs->channelCount()); QVERIFY(dev->dataManager() != 0); KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test"); KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125); dev = new KisPaintDevice(layer.data(), cs); QVERIFY(*dev->colorSpace() == *cs); QVERIFY(dev->x() == 0); QVERIFY(dev->y() == 0); QVERIFY(dev->pixelSize() == cs->pixelSize()); QVERIFY(dev->channelCount() == cs->channelCount()); QVERIFY(dev->dataManager() != 0); // Let the layer go out of scope and see what happens { KisPaintLayerSP l2 = new KisPaintLayer(image, "blabla", 250); dev = new KisPaintDevice(l2.data(), cs); } } void KisPaintDeviceTest::testStore() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KoStore * readStore = KoStore::createStore(QString(FILES_DATA_DIR) + QDir::separator() + "store_test.kra", KoStore::Read); readStore->open("built image/layers/layer0"); QVERIFY(dev->read(readStore->device())); readStore->close(); delete readStore; QVERIFY(dev->exactBounds() == QRect(0, 0, 100, 100)); KoStore * writeStore = KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Write); KisFakePaintDeviceWriter fakeWriter(writeStore); writeStore->open("built image/layers/layer0"); QVERIFY(dev->write(fakeWriter)); writeStore->close(); delete writeStore; KisPaintDeviceSP dev2 = new KisPaintDevice(cs); readStore = KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Read); readStore->open("built image/layers/layer0"); QVERIFY(dev2->read(readStore->device())); readStore->close(); delete readStore; QVERIFY(dev2->exactBounds() == QRect(0, 0, 100, 100)); QPoint pt; if (!TestUtil::comparePaintDevices(pt, dev, dev2)) { QFAIL(QString("Loading a saved image is not pixel perfect, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1()); } } void KisPaintDeviceTest::testGeometry() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, pixel); dev->fill(0, 0, 512, 512, pixel); QCOMPARE(dev->exactBounds(), QRect(0, 0, 512, 512)); QCOMPARE(dev->extent(), QRect(0, 0, 512, 512)); dev->moveTo(10, 10); QCOMPARE(dev->exactBounds(), QRect(10, 10, 512, 512)); QCOMPARE(dev->extent(), QRect(10, 10, 512, 512)); dev->crop(50, 50, 50, 50); QCOMPARE(dev->exactBounds(), QRect(50, 50, 50, 50)); QCOMPARE(dev->extent(), QRect(10, 10, 128, 128)); QColor c; dev->clear(QRect(50, 50, 50, 50)); dev->pixel(80, 80, &c); QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8); dev->fill(0, 0, 512, 512, pixel); dev->pixel(80, 80, &c); QVERIFY(c == Qt::white); QVERIFY(c.alpha() == OPACITY_OPAQUE_U8); dev->clear(); dev->pixel(80, 80, &c); QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8); QVERIFY(dev->extent().isEmpty()); QVERIFY(dev->exactBounds().isEmpty()); } void KisPaintDeviceTest::testClear() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); dev->clear(); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); QRect fillRect1(50, 100, 150, 100); dev->fill(fillRect1, KoColor(Qt::red, cs)); QCOMPARE(dev->extent(), QRect(0, 64, 256, 192)); QCOMPARE(dev->exactBounds(), fillRect1); dev->clear(QRect(100, 100, 100, 100)); QCOMPARE(dev->extent(), QRect(0, 64, 256, 192)); QCOMPARE(dev->exactBounds(), QRect(50, 100, 50, 100)); dev->clear(); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); } void KisPaintDeviceTest::testCrop() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, pixel); dev->fill(-14, 8, 433, 512, pixel); QVERIFY(dev->exactBounds() == QRect(-14, 8, 433, 512)); // Crop inside dev->crop(50, 50, 150, 150); QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150)); // Crop outside, pd should not grow dev->crop(0, 0, 1000, 1000); QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150)); } void KisPaintDeviceTest::testRoundtripReadWrite() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); dev->convertFromQImage(image, 0); quint8* bytes = new quint8[cs->pixelSize() * image.width() * image.height()]; memset(bytes, 0, image.width() * image.height() * dev->pixelSize()); dev->readBytes(bytes, image.rect()); KisPaintDeviceSP dev2 = new KisPaintDevice(cs); dev2->writeBytes(bytes, image.rect()); QVERIFY(dev2->exactBounds() == image.rect()); dev2->convertToQImage(0, 0, 0, image.width(), image.height()).save("readwrite.png"); QPoint pt; if (!TestUtil::comparePaintDevices(pt, dev, dev2)) { QFAIL(QString("Failed round trip using readBytes and writeBytes, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1()); } } void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs) { QString profile1("no profile"); QString profile2("no profile"); if (srcCs->profile()) profile1 = srcCs->profile()->name(); if (dstCs->profile()) profile2 = dstCs->profile()->name(); QWARN(QString("Failed %1 %2 -> %3 %4 %5") .arg(srcCs->name()) .arg(profile1) .arg(dstCs->name()) .arg(profile2) .arg(reason) .toLatin1()); } void KisPaintDeviceTest::testColorSpaceConversion() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->lab16(); KisPaintDeviceSP dev = new KisPaintDevice(srcCs); dev->convertFromQImage(image, 0); dev->moveTo(10, 10); // Unalign with tile boundaries - KUndo2Command* cmd = dev->convertTo(dstCs); + KUndo2Command* cmd = new KUndo2Command(); + dev->convertTo(dstCs, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags(), + cmd); QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height())); QCOMPARE(dev->pixelSize(), dstCs->pixelSize()); QVERIFY(*dev->colorSpace() == *dstCs); cmd->redo(); cmd->undo(); QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height())); QCOMPARE(dev->pixelSize(), srcCs->pixelSize()); QVERIFY(*dev->colorSpace() == *srcCs); delete cmd; } void KisPaintDeviceTest::testRoundtripConversion() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); QImage result = dev->convertToQImage(0, 0, 0, 640, 441); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_paint_device_test_test_roundtrip_qimage.png"); result.save("kis_paint_device_test_test_roundtrip_result.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testFastBitBlt() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dstDev = new KisPaintDevice(cs); KisPaintDeviceSP srcDev = new KisPaintDevice(cs); srcDev->convertFromQImage(image, 0); QRect cloneRect(100,100,200,200); QPoint errpoint; QVERIFY(dstDev->fastBitBltPossible(srcDev)); dstDev->fastBitBlt(srcDev, cloneRect); QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } // Test Rough version dstDev->clear(); dstDev->fastBitBltRough(srcDev, cloneRect); srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } srcDev->moveTo(10,10); QVERIFY(!dstDev->fastBitBltPossible(srcDev)); } void KisPaintDeviceTest::testMakeClone() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP srcDev = new KisPaintDevice(cs); srcDev->convertFromQImage(image, 0); srcDev->moveTo(10,10); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->lab16(); KisPaintDeviceSP dstDev = new KisPaintDevice(weirdCS); dstDev->moveTo(1000,1000); QVERIFY(!dstDev->fastBitBltPossible(srcDev)); QRect cloneRect(100,100,200,200); QPoint errpoint; dstDev->makeCloneFrom(srcDev, cloneRect); QVERIFY(*dstDev->colorSpace() == *srcDev->colorSpace()); QCOMPARE(dstDev->pixelSize(), srcDev->pixelSize()); QCOMPARE(dstDev->x(), srcDev->x()); QCOMPARE(dstDev->y(), srcDev->y()); QCOMPARE(dstDev->exactBounds(), cloneRect); QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, dstImage, srcImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testThumbnail() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); { KisPaintDeviceSP thumb = dev->createThumbnailDevice(50, 50); QRect rc = thumb->exactBounds(); QVERIFY(rc.width() <= 50); QVERIFY(rc.height() <= 50); } { QImage thumb = dev->createThumbnail(50, 50); QVERIFY(!thumb.isNull()); QVERIFY(thumb.width() <= 50); QVERIFY(thumb.height() <= 50); image.save("kis_paint_device_test_test_thumbnail.png"); } } void KisPaintDeviceTest::testThumbnailDeviceWithOffset() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); dev->setX(10); dev->setY(10); QImage thumb = dev->createThumbnail(640,441,QRect(10,10,640,441)); image.save("oring.png"); thumb.save("thumb.png"); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, thumb, image)); } void KisPaintDeviceTest::testCaching() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* whitePixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, whitePixel); quint8* blackPixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::black, blackPixel); dev->fill(0, 0, 512, 512, whitePixel); QImage thumb1 = dev->createThumbnail(50, 50); QRect exactBounds1 = dev->exactBounds(); dev->fill(0, 0, 768, 768, blackPixel); QImage thumb2 = dev->createThumbnail(50, 50); QRect exactBounds2 = dev->exactBounds(); dev->moveTo(10, 10); QImage thumb3 = dev->createThumbnail(50, 50); QRect exactBounds3 = dev->exactBounds(); dev->crop(50, 50, 50, 50); QImage thumb4 = dev->createThumbnail(50, 50); QRect exactBounds4 = dev->exactBounds(); QVERIFY(thumb1 != thumb2); QVERIFY(thumb2 == thumb3); // Cache miss, but image is the same QVERIFY(thumb3 != thumb4); QVERIFY(thumb4 != thumb1); QCOMPARE(exactBounds1, QRect(0,0,512,512)); QCOMPARE(exactBounds2, QRect(0,0,768,768)); QCOMPARE(exactBounds3, QRect(10,10,768,768)); QCOMPARE(exactBounds4, QRect(50,50,50,50)); } void KisPaintDeviceTest::testRegion() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* whitePixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, whitePixel); dev->fill(0, 0, 10, 10, whitePixel); dev->fill(70, 70, 10, 10, whitePixel); dev->fill(129, 0, 10, 10, whitePixel); dev->fill(0, 1030, 10, 10, whitePixel); QRegion referenceRegion; referenceRegion += QRect(0,0,64,64); referenceRegion += QRect(64,64,64,64); referenceRegion += QRect(128,0,64,64); referenceRegion += QRect(0,1024,64,64); QCOMPARE(dev->exactBounds(), QRect(0,0,139,1040)); QCOMPARE(dev->region(), referenceRegion); } void KisPaintDeviceTest::testPixel() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QColor c = Qt::red; quint8 opacity = 125; c.setAlpha(opacity); dev->setPixel(5, 5, c); QColor c2; dev->pixel(5, 5, &c2); QVERIFY(c == c2); QVERIFY(opacity == c2.alpha()); } void KisPaintDeviceTest::testPlanarReadWrite() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(QColor(255, 200, 155, 100), pixel); dev->fill(0, 0, 5000, 5000, pixel); delete[] pixel; QColor c1; dev->pixel(5, 5, &c1); QVector planes = dev->readPlanarBytes(500, 500, 100, 100); QVector swappedPlanes; QCOMPARE((int)planes.size(), (int)dev->channelCount()); for (int i = 0; i < 100*100; i++) { // BGRA encoded QVERIFY(planes.at(2)[i] == 255); QVERIFY(planes.at(1)[i] == 200); QVERIFY(planes.at(0)[i] == 155); QVERIFY(planes.at(3)[i] == 100); } for (uint i = 1; i < dev->channelCount() + 1; ++i) { swappedPlanes.append(planes[dev->channelCount() - i]); } dev->writePlanarBytes(swappedPlanes, 0, 0, 100, 100); dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar.png"); dev->pixel(5, 5, &c1); QVERIFY(c1.red() == 200); QVERIFY(c1.green() == 255); QVERIFY(c1.blue() == 100); QVERIFY(c1.alpha() == 155); dev->pixel(75, 50, &c1); QVERIFY(c1.red() == 200); QVERIFY(c1.green() == 255); QVERIFY(c1.blue() == 100); QVERIFY(c1.alpha() == 155); // check if one of the planes is Null. Q_ASSERT(planes.size() == 4); delete [] planes[2]; planes[2] = 0; dev->writePlanarBytes(planes, 0, 0, 100, 100); dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar_noR.png"); dev->pixel(75, 50, &c1); QCOMPARE(c1.red(), 200); QCOMPARE(c1.green(), 200); QCOMPARE(c1.blue(), 155); QCOMPARE(c1.alpha(), 100); QVector::iterator i; for (i = planes.begin(); i != planes.end(); ++i) { delete [] *i; } swappedPlanes.clear(); } void KisPaintDeviceTest::testBltPerformance() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP fdev = new KisPaintDevice(cs); fdev->convertFromQImage(image, 0); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data()); QTime t; t.start(); int x; #ifdef LIMIT_LONG_TESTS int steps = 10; #else int steps = 1000; #endif for (x = 0; x < steps; ++x) { KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), fdev, image.rect()); } dbgKrita << x << "blits" << " done in " << t.elapsed() << "ms"; } void KisPaintDeviceTest::testDeviceDuplication() { QRect fillRect(0,0,64,64); quint8 fillPixel[4]={255,255,255,255}; QRect clearRect(10,10,20,20); QImage referenceImage; QImage resultImage; const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP device = new KisPaintDevice(cs); // dbgKrita<<"FILLING"; device->fill(fillRect.left(), fillRect.top(), fillRect.width(), fillRect.height(),fillPixel); referenceImage = device->convertToQImage(0); KisTransaction transaction1(device); // dbgKrita<<"CLEARING"; device->clear(clearRect); transaction1.revert(); resultImage = device->convertToQImage(0); QVERIFY(resultImage == referenceImage); KisPaintDeviceSP clone = new KisPaintDevice(*device); KisTransaction transaction(clone); // dbgKrita<<"CLEARING"; clone->clear(clearRect); transaction.revert(); resultImage = clone->convertToQImage(0); QVERIFY(resultImage == referenceImage); } void KisPaintDeviceTest::testTranslate() { QRect fillRect(0,0,64,64); quint8 fillPixel[4]={255,255,255,255}; const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP device = new KisPaintDevice(cs); device->fill(fillRect.left(), fillRect.top(), fillRect.width(), fillRect.height(),fillPixel); device->setX(-10); device->setY(10); QCOMPARE(device->exactBounds(), QRect(-10,10,64,64)); QCOMPARE(device->extent(), QRect(-10,10,64,64)); } void KisPaintDeviceTest::testOpacity() { // blt a semi-transparent image on a white paint device QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP fdev = new KisPaintDevice(cs); fdev->convertFromQImage(image, 0); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data()); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), fdev, image.rect()); QImage result = dev->convertToQImage(0, 0, 0, 640, 441); QImage checkResult(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent_result.png"); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, checkResult, result, 1)) { checkResult.save("kis_paint_device_test_test_blt_fixed_opactiy_expected.png"); result.save("kis_paint_device_test_test_blt_fixed_opacity_result.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testExactBoundsWeirdNullAlphaCase() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); dev->fill(QRect(10,10,10,10), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(10,10,10,10)); const quint8 weirdPixelData[4] = {0,10,0,0}; KoColor weirdColor(weirdPixelData, cs); dev->setPixel(6,6,weirdColor); // such weird pixels should not change our opinion about // device's size QCOMPARE(dev->exactBounds(), QRect(10,10,10,10)); } void KisPaintDeviceTest::benchmarkExactBoundsNullDefaultPixel() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QRect fillRect(60,60, 1930, 1930); dev->fill(fillRect, KoColor(Qt::white, cs)); QRect measuredRect; QBENCHMARK { // invalidate the cache dev->setDirty(); measuredRect = dev->exactBounds(); } QCOMPARE(measuredRect, fillRect); } void KisPaintDeviceTest::testAmortizedExactBounds() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QRect fillRect(60,60, 833, 833); QRect extent(0,0,896,896); dev->fill(fillRect, KoColor(Qt::white, cs)); QEXPECT_FAIL("", "Expecting the extent, we somehow get the fillrect", Continue); QCOMPARE(dev->exactBounds(), extent); QCOMPARE(dev->extent(), extent); QCOMPARE(dev->exactBoundsAmortized(), fillRect); dev->setDirty(); QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue); QCOMPARE(dev->exactBoundsAmortized(), fillRect); dev->setDirty(); QCOMPARE(dev->exactBoundsAmortized(), extent); QTest::qSleep(1100); QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue); QCOMPARE(dev->exactBoundsAmortized(), fillRect); } void KisPaintDeviceTest::testNonDefaultPixelArea() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); KoColor defPixel(Qt::red, cs); dev->setDefaultPixel(defPixel); QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); QRect fillRect(10,11,18,14); dev->fill(fillRect, KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QCOMPARE(dev->nonDefaultPixelArea(), fillRect); // non-default pixel variant should also handle weird pixels const quint8 weirdPixelData[4] = {0,10,0,0}; KoColor weirdColor(weirdPixelData, cs); dev->setPixel(100,100,weirdColor); // such weird pixels should not change our opinion about // device's size QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QCOMPARE(dev->nonDefaultPixelArea(), fillRect | QRect(100,100,1,1)); } void KisPaintDeviceTest::testExactBoundsNonTransparent() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test"); KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125); KisPaintDeviceSP dev = layer->paintDevice(); QVERIFY(dev); QRect imageRect(0,0,1000,1000); KoColor defPixel(Qt::red, cs); dev->setDefaultPixel(defPixel); QCOMPARE(dev->exactBounds(), imageRect); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); KoColor fillPixel(Qt::white, cs); dev->fill(imageRect, KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), imageRect); QCOMPARE(dev->nonDefaultPixelArea(), imageRect); dev->fill(QRect(1000,0, 1, 1000), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1000)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1000)); dev->fill(QRect(0,1000, 1000, 1), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1001)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1001)); dev->fill(QRect(0,-1, 1000, 1), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,-1,1001,1002)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,-1,1001,1002)); dev->fill(QRect(-1,0, 1, 1000), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(-1,-1,1002,1002)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(-1,-1,1002,1002)); } KisPaintDeviceSP createWrapAroundPaintDevice(const KoColorSpace *cs) { struct TestingDefaultBounds : public KisDefaultBoundsBase { QRect bounds() const override { return QRect(0,0,20,20); } bool wrapAroundMode() const override { return true; } int currentLevelOfDetail() const override { return 0; } int currentTime() const override { return 0; } bool externalFrameActive() const override { return false; } }; KisPaintDeviceSP dev = new KisPaintDevice(cs); KisDefaultBoundsBaseSP bounds = new TestingDefaultBounds(); dev->setDefaultBounds(bounds); return dev; } void checkReadWriteRoundTrip(KisPaintDeviceSP dev, const QRect &rc) { KisPaintDeviceSP deviceCopy = new KisPaintDevice(*dev.data()); QRect readRect(10, 10, 20, 20); int bufSize = rc.width() * rc.height() * dev->pixelSize(); QScopedArrayPointer buf1(new quint8[bufSize]); deviceCopy->readBytes(buf1.data(), rc); deviceCopy->clear(); QVERIFY(deviceCopy->extent().isEmpty()); QScopedArrayPointer buf2(new quint8[bufSize]); deviceCopy->writeBytes(buf1.data(), rc); deviceCopy->readBytes(buf2.data(), rc); QVERIFY(!memcmp(buf1.data(), buf2.data(), bufSize)); } void KisPaintDeviceTest::testReadBytesWrapAround() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); const int pixelSize = dev->pixelSize(); { QRect readRect(10, 10, 20, 20); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final1.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check weird case when the read rect is larger than wrap rect QRect readRect(10, 10, 30, 30); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final2.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // even more large QRect readRect(10, 10, 40, 40); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final3.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap rect contains the read rect entirely QRect readRect(1, 1, 10, 10); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final4.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap happens only on vertical side of the rect QRect readRect(1, 1, 29, 10); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final5.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (21 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (22 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap happens only on horizontal side of the rect QRect readRect(1, 1, 10, 29); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final6.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 21) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 22) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } } #include "kis_random_accessor_ng.h" void KisPaintDeviceTest::testWrappedRandomAccessor() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); const int pixelSize = dev->pixelSize(); int x; int y; x = 3; y = 3; KisRandomAccessorSP dstIt = dev->createRandomAccessorNG(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 23; y = 23; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 3; y = 23; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 23; y = 3; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = -17; y = 3; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 3; y = -17; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = -17; y = -17; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); } #include "kis_iterator_ng.h" static bool nextRowGeneral(KisHLineIteratorSP it, int y, const QRect &rc) { it->nextRow(); return y < rc.height(); } static bool nextRowGeneral(KisVLineIteratorSP it, int y, const QRect &rc) { it->nextColumn(); return y < rc.width(); } template bool checkXY(const QPoint &pt, const QPoint &realPt) { Q_UNUSED(pt); Q_UNUSED(realPt); return false; } template <> bool checkXY(const QPoint &pt, const QPoint &realPt) { return pt == realPt; } template <> bool checkXY(const QPoint &pt, const QPoint &realPt) { return pt.x() == realPt.y() && pt.y() == realPt.x(); } #include template bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { Q_UNUSED(value); Q_UNUSED(pt); Q_UNUSED(wrappedRect); return false; } template <> bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { int x = KisWrappedRect::xToWrappedX(pt.x(), wrappedRect.wrapRect()); int borderX = wrappedRect.originalRect().x() + wrappedRect.wrapRect().width(); int conseq = x >= borderX ? wrappedRect.wrapRect().right() - x + 1 : borderX - x; conseq = qMin(conseq, wrappedRect.originalRect().right() - pt.x() + 1); return value == conseq; } template <> bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { Q_UNUSED(pt); Q_UNUSED(wrappedRect); return value == 1; } template IteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { Q_UNUSED(dev); Q_UNUSED(rc); return 0; } template <> KisHLineIteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { return dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); } template <> KisVLineIteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { return dev->createVLineIteratorNG(rc.x(), rc.y(), rc.height()); } template void testWrappedLineIterator(QString testName, const QRect &rect) { testName = QString("%1_%2_%3_%4_%5") .arg(testName) .arg(rect.x()) .arg(rect.y()) .arg(rect.width()) .arg(rect.height()); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); // test rect fits the wrap rect in both dimensions IteratorSP it = createIterator(dev, rect); int y = 0; do { int x = 0; do { quint8 *data = it->rawData(); data[0] = 10 * x; data[1] = 10 * y; data[2] = 0; data[3] = 255; x++; } while (it->nextPixel()); } while (nextRowGeneral(it, ++y, rect)); QRect rc = dev->defaultBounds()->bounds() | dev->exactBounds(); QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators", testName)); } template void testWrappedLineIterator(const QString &testName) { testWrappedLineIterator(testName, QRect(10,10,20,20)); testWrappedLineIterator(testName, QRect(10,10,10,20)); testWrappedLineIterator(testName, QRect(10,10,20,10)); testWrappedLineIterator(testName, QRect(10,10,10,10)); testWrappedLineIterator(testName, QRect(0,0,20,20)); } void KisPaintDeviceTest::testWrappedHLineIterator() { testWrappedLineIterator("hline_iterator"); } void KisPaintDeviceTest::testWrappedVLineIterator() { testWrappedLineIterator("vline_iterator"); } template void testWrappedLineIteratorReadMoreThanBounds(QString testName) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KisPaintDeviceSP dst = new KisPaintDevice(cs); // fill device with a gradient QRect bounds = dev->defaultBounds()->bounds(); for (int y = bounds.y(); y < bounds.y() + bounds.height(); y++) { for (int x = bounds.x(); x < bounds.x() + bounds.width(); x++) { QColor c((10 * x) % 255, (10 * y) % 255, 0, 255); dev->setPixel(x, y, c); } } // test rect doesn't fit the wrap rect in both dimensions const QRect &rect(bounds.adjusted(-6,-6,8,8)); KisRandomAccessorSP dstIt = dst->createRandomAccessorNG(rect.x(), rect.y()); IteratorSP it = createIterator(dev, rect); for (int y = rect.y(); y < rect.y() + rect.height(); y++) { for (int x = rect.x(); x < rect.x() + rect.width(); x++) { quint8 *data = it->rawData(); QVERIFY(checkConseqPixels(it->nConseqPixels(), QPoint(x, y), KisWrappedRect(rect, bounds))); dstIt->moveTo(x, y); memcpy(dstIt->rawData(), data, cs->pixelSize()); QVERIFY(checkXY(QPoint(it->x(), it->y()), QPoint(x,y))); bool stepDone = it->nextPixel(); QCOMPARE(stepDone, x < rect.x() + rect.width() - 1); } if (!nextRowGeneral(it, y, rect)) break; } testName = QString("%1_%2_%3_%4_%5") .arg(testName) .arg(rect.x()) .arg(rect.y()) .arg(rect.width()) .arg(rect.height()); QRect rc = rect; QImage result = dst->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QImage ref = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators_huge", testName, 1)); } void KisPaintDeviceTest::testWrappedHLineIteratorReadMoreThanBounds() { testWrappedLineIteratorReadMoreThanBounds("hline_iterator"); } void KisPaintDeviceTest::testWrappedVLineIteratorReadMoreThanBounds() { testWrappedLineIteratorReadMoreThanBounds("vline_iterator"); } void KisPaintDeviceTest::testMoveWrapAround() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); // QRect rc = dev->defaultBounds()->bounds(); //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move0.png"); QCOMPARE(dev->exactBounds(), QRect(3,3,16,16)); dev->moveTo(QPoint(10,10)); QCOMPARE(dev->exactBounds(), QRect(8,8,6,6)); //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move1.png"); } #include "kis_lock_free_cache.h" #define NUM_TYPES 3 // high-concurrency #define NUM_CYCLES 500000 #define NUM_THREADS 4 struct TestingCache : KisLockFreeCache { int calculateNewValue() const override { return m_realValue; } QAtomicInt m_realValue; }; class CacheStressJob : public QRunnable { public: CacheStressJob(TestingCache &cache) : m_cache(cache), m_oldValue(0) { } void run() override { for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = i % NUM_TYPES; switch(type) { case 0: m_cache.m_realValue.ref(); m_oldValue = m_cache.m_realValue; m_cache.invalidate(); break; case 1: { int newValue = m_cache.getValue(); Q_ASSERT(newValue >= m_oldValue); Q_UNUSED(newValue); } break; case 3: QTest::qSleep(3); break; } } } private: TestingCache &m_cache; int m_oldValue; }; void KisPaintDeviceTest::testCacheState() { TestingCache cache; QList jobsList; CacheStressJob *job; for(qint32 i = 0; i < NUM_THREADS; i++) { //job = new CacheStressJob(value, cacheValue, cacheState); job = new CacheStressJob(cache); job->setAutoDelete(true); jobsList.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); Q_FOREACH (job, jobsList) { pool.start(job); } pool.waitForDone(); } struct TestingLodDefaultBounds : public KisDefaultBoundsBase { TestingLodDefaultBounds(const QRect &bounds = QRect(0,0,100,100)) : m_lod(0), m_bounds(bounds) {} QRect bounds() const override { return m_bounds; } bool wrapAroundMode() const override { return false; } int currentLevelOfDetail() const override { return m_lod; } int currentTime() const override { return 0; } bool externalFrameActive() const override { return false; } void testingSetLevelOfDetail(int lod) { m_lod = lod; } private: int m_lod; QRect m_bounds; }; void fillGradientDevice(KisPaintDeviceSP dev, const QRect &rect, bool flat = false) { if (flat) { dev->fill(rect, KoColor(Qt::red, dev->colorSpace())); } else { // fill device with a gradient KisSequentialIterator it(dev, rect); while (it.nextPixel()) { QColor c((10 * it.x()) & 0xFF, (10 * it.y()) & 0xFF, 0, 255); KoColor color(c, dev->colorSpace()); memcpy(it.rawData(), color.data(), dev->pixelSize()); } } } #include "kis_lod_transform.h" void KisPaintDeviceTest::testLodTransform() { const int lod = 2; // round to 4 KisLodTransform t(lod); QRect rc1(-16, -16, 8, 8); QRect rc2(-16, -16, 7, 7); QRect rc3(-15, -15, 7, 7); QCOMPARE(t.alignedRect(rc1, lod), rc1); QCOMPARE(t.alignedRect(rc2, lod), rc1); QCOMPARE(t.alignedRect(rc3, lod), rc1); } #include "krita_utils.h" void syncLodCache(KisPaintDeviceSP dev, int levelOfDetail) { KisPaintDevice::LodDataStruct* s = dev->createLodDataStruct(levelOfDetail); QRegion region = dev->regionForLodSyncing(); Q_FOREACH(QRect rect2, KritaUtils::splitRegionIntoPatches(region, KritaUtils::optimalPatchSize())) { dev->updateLodDataStruct(s, rect2); } dev->uploadLodDataStruct(s); } void KisPaintDeviceTest::testLodDevice() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(); dev->setDefaultBounds(bounds); // fill device with a gradient // QRect rect = dev->defaultBounds()->bounds(); // fillGradientDevice(dev, rect); fillGradientDevice(dev, QRect(50,50,30,30)); QCOMPARE(dev->exactBounds(), QRect(50,50,30,30)); QImage result; qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "initial")); bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); QCOMPARE(dev->exactBounds(), QRect(25,25,15,15)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1")); bounds->testingSetLevelOfDetail(2); QCOMPARE(dev->exactBounds(), QRect(25,25,15,15)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1")); syncLodCache(dev, 2); QCOMPARE(dev->exactBounds(), QRect(12,12,8,8)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod2")); bounds->testingSetLevelOfDetail(0); dev->setX(20); dev->setY(10); bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); QCOMPARE(dev->exactBounds(), QRect(35,30,15,15)); qDebug() << ppVar(dev->exactBounds()) << ppVar(dev->x()) << ppVar(dev->y()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1-offset-6-14")); } void KisPaintDeviceTest::benchmarkLod1Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); } } void KisPaintDeviceTest::benchmarkLod2Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(2); syncLodCache(dev, 2); } } void KisPaintDeviceTest::benchmarkLod3Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(3); syncLodCache(dev, 3); } } void KisPaintDeviceTest::benchmarkLod4Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(4); syncLodCache(dev, 4); } } #include "kis_keyframe_channel.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "testing_timed_default_bounds.h" void KisPaintDeviceTest::testFramesLeaking() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data is kept separate o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add keyframe at position 10 channel->addKeyframe(10); // two frames, m_data has a default empty value o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); // add keyframe at position 20 channel->addKeyframe(20); // three frames, m_data is default, current frame is 0 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 3); QVERIFY(o.m_currentData == o.m_frames[0]); // switch to frame 10 bounds->testingSetTime(10); // three frames, m_data is default, current frame is 10 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[1]); QCOMPARE(o.m_frames.size(), 3); // switch to frame 20 bounds->testingSetTime(20); // three frames, m_data is default, current frame is 20 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[2]); QCOMPARE(o.m_frames.size(), 3); // switch to frame 15 bounds->testingSetTime(15); // three frames, m_data is default, current frame is 10 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[1]); QCOMPARE(o.m_frames.size(), 3); KisKeyframeSP key; // deletion of frame 0 is forbidden key = channel->keyframeAt(0); QVERIFY(key); QVERIFY(channel->deleteKeyframe(key)); // delete keyframe at position 11 key = channel->activeKeyframeAt(11); QVERIFY(key); QCOMPARE(key->time(), 10); QVERIFY(channel->deleteKeyframe(key)); // two frames, m_data is default, current frame is 0 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 2); // deletion of frame 0 is forbidden key = channel->activeKeyframeAt(11); QVERIFY(key); QCOMPARE(key->time(), 0); QVERIFY(channel->deleteKeyframe(key)); // nothing changed o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 2); // delete keyframe at position 20 key = channel->activeKeyframeAt(20); QVERIFY(key); QCOMPARE(key->time(), 20); QVERIFY(channel->deleteKeyframe(key)); // one keyframe is left at position 0, m_data is default o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 1); // ensure all the objects in the list of all objects are unique QList allObjects = i->testingGetDataObjectsList(); QSet uniqueObjects; Q_FOREACH (KisPaintDeviceData *obj, allObjects) { if (!obj) continue; QVERIFY(!uniqueObjects.contains(obj)); uniqueObjects.insert(obj); } } void KisPaintDeviceTest::testFramesUndoRedo() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; int frameId = -1; const int time = 1; channel->addKeyframe(time, &cmdAdd); frameId = channel->frameIdAt(time); //int frameId = i->createFrame(false, 0, QPoint(), &cmdAdd); QCOMPARE(frameId, 1); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.undo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.redo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdRemove; KisKeyframeSP keyframe = channel->keyframeAt(time); QVERIFY(keyframe); channel->deleteKeyframe(keyframe, &cmdRemove); //i->deleteFrame(1, &cmdRemove); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.redo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); } void KisPaintDeviceTest::testFramesUndoRedoWithChannel() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.undo(); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.redo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdRemove; channel->deleteKeyframe(frame, &cmdRemove); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.redo(); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdMove; channel->moveKeyframe(frame, 12, &cmdMove); QVERIFY(!channel->keyframeAt(10)); QVERIFY(channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdMove.undo(); QVERIFY(channel->keyframeAt(10)); QVERIFY(!channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdMove.redo(); QVERIFY(!channel->keyframeAt(10)); QVERIFY(channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); } void fillRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds) { KUndo2Command parentCommand; KisRasterKeyframeChannel *channel = dev->keyframeChannel(); KisKeyframeSP frame = channel->addKeyframe(time, &parentCommand); const int oldTime = bounds->currentTime(); bounds->testingSetTime(time); KoColor color(Qt::red, dev->colorSpace()); dev->fill(rc, color); bounds->testingSetTime(oldTime); } bool checkRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds) { const int oldTime = bounds->currentTime(); bounds->testingSetTime(time); bool result = dev->exactBounds() == rc; if (!result) { qDebug() << "Failed to check frame:" << ppVar(time) << ppVar(rc) << ppVar(dev->exactBounds()); } bounds->testingSetTime(oldTime); return result; } void testCrossDeviceFrameCopyImpl(bool useChannel) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev1 = new KisPaintDevice(cs); KisPaintDeviceSP dev2 = new KisPaintDevice(cs); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); KisPaintDeviceSP dev3 = new KisPaintDevice(cs3); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev1->setDefaultBounds(bounds); dev2->setDefaultBounds(bounds); dev3->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel1 = dev1->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i1 = dev1->framesInterface(); QVERIFY(channel1); QVERIFY(i1); KisRasterKeyframeChannel *channel2 = dev2->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i2 = dev2->framesInterface(); QVERIFY(channel2); QVERIFY(i2); KisRasterKeyframeChannel *channel3 = dev3->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i3 = dev3->framesInterface(); QVERIFY(channel3); QVERIFY(i3); fillRect(dev1, 10, QRect(100,100,100,100), bounds); fillRect(dev2, 20, QRect(200,200,100,100), bounds); fillRect(dev3, 30, QRect(300,300,100,100), bounds); QCOMPARE(dev1->exactBounds(), QRect()); const int dstFrameId1 = channel1->frameIdAt(10); const int srcFrameId2 = channel2->frameIdAt(20); const int srcFrameId3 = channel3->frameIdAt(30); KUndo2Command cmd1; if (!useChannel) { dev1->framesInterface()->uploadFrame(srcFrameId2, dstFrameId1, dev2); } else { KisKeyframeSP k = channel1->copyExternalKeyframe(channel2, 20, 10, &cmd1); } QCOMPARE(dev1->exactBounds(), QRect()); QVERIFY(checkRect(dev1, 10, QRect(200,200,100,100), bounds)); if (useChannel) { cmd1.undo(); QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds)); } KUndo2Command cmd2; if (!useChannel) { dev1->framesInterface()->uploadFrame(srcFrameId3, dstFrameId1, dev3); } else { KisKeyframeSP k = channel1->copyExternalKeyframe(channel3, 30, 10, &cmd2); } QCOMPARE(dev1->exactBounds(), QRect()); QVERIFY(checkRect(dev1, 10, QRect(300,300,100,100), bounds)); if (useChannel) { cmd2.undo(); QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds)); } } void KisPaintDeviceTest::testCrossDeviceFrameCopyDirect() { testCrossDeviceFrameCopyImpl(false); } void KisPaintDeviceTest::testCrossDeviceFrameCopyChannel() { testCrossDeviceFrameCopyImpl(true); } #include "kis_surrogate_undo_adapter.h" void KisPaintDeviceTest::testLazyFrameCreation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); bounds->testingSetTime(10); QCOMPARE(i->frames().size(), 1); KisSurrogateUndoAdapter undoAdapter; { KisTransaction transaction1(dev); transaction1.commit(&undoAdapter); } QCOMPARE(i->frames().size(), 2); undoAdapter.undoAll(); QCOMPARE(i->frames().size(), 1); undoAdapter.redoAll(); QCOMPARE(i->frames().size(), 2); } void KisPaintDeviceTest::testCopyPaintDeviceWithFrames() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); //QVERIFY(o.m_currentData == o.m_frames[0]); KisPaintDeviceSP newDev = new KisPaintDevice(*dev, KritaUtils::CopyAllFrames); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(10)); } #include #include #include #include #include #include #include #include "KoCompositeOpRegistry.h" using namespace boost::accumulators; accumulator_set > accum; void KisPaintDeviceTest::testCompositionAssociativity() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); qsrand(500); boost::mt11213b _rnd0(qrand()); boost::mt11213b _rnd1(qrand()); boost::mt11213b _rnd2(qrand()); boost::mt11213b _rnd3(qrand()); boost::uniform_smallint rnd0(0, 255); boost::uniform_smallint rnd1(0, 255); boost::uniform_smallint rnd2(0, 255); boost::uniform_smallint rnd3(0, 255); QList allCompositeOps = cs->compositeOps(); Q_FOREACH (const KoCompositeOp *op, allCompositeOps) { accumulator_set > accum; const int numIterations = 10000; for (int j = 0; j < numIterations; j++) { KoColor c1(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor c2(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor c3(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); //KoColor c4(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); //KoColor c5(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor r1(QColor(Qt::transparent), cs); KoColor r2(QColor(Qt::transparent), cs); KoColor r3(QColor(Qt::transparent), cs); op->composite(r1.data(), 0, c1.data(), 0, 0,0, 1,1, 255); op->composite(r1.data(), 0, c2.data(), 0, 0,0, 1,1, 255); op->composite(r1.data(), 0, c3.data(), 0, 0,0, 1,1, 255); //op->composite(r1.data(), 0, c4.data(), 0, 0,0, 1,1, 255); //op->composite(r1.data(), 0, c5.data(), 0, 0,0, 1,1, 255); op->composite(r3.data(), 0, c2.data(), 0, 0,0, 1,1, 255); op->composite(r3.data(), 0, c3.data(), 0, 0,0, 1,1, 255); //op->composite(r3.data(), 0, c4.data(), 0, 0,0, 1,1, 255); //op->composite(r3.data(), 0, c5.data(), 0, 0,0, 1,1, 255); op->composite(r2.data(), 0, c1.data(), 0, 0,0, 1,1, 255); op->composite(r2.data(), 0, r3.data(), 0, 0,0, 1,1, 255); const quint8 *p1 = r1.data(); const quint8 *p2 = r2.data(); if (memcmp(p1, p2, 4) != 0) { for (int i = 0; i < 4; i++) { accum(qAbs(p1[i] - p2[i])); } } } qDebug("Errors for op %25s err rate %7.2f var %7.2f max %7.2f", op->id().toLatin1().data(), (qreal(count(accum)) / (4 * numIterations)), variance(accum), count(accum) > 0 ? (max)(accum) : 0); } } #include struct FillWorker : public QRunnable { FillWorker(KisPaintDeviceSP dev, const QRect &fillRect, bool clear) : m_dev(dev), m_fillRect(fillRect), m_clear(clear) { setAutoDelete(true); } void run() { if (m_clear) { m_dev->clear(m_fillRect); } else { const KoColor fillColor(Qt::red, m_dev->colorSpace()); const int pixelSize = m_dev->colorSpace()->pixelSize(); KisSequentialIterator it(m_dev, m_fillRect); while (it.nextPixel()) { memcpy(it.rawData(), fillColor.data(), pixelSize); } } } private: KisPaintDeviceSP m_dev; QRect m_fillRect; bool m_clear; }; #ifdef Q_OS_LINUX #include #endif void KisPaintDeviceTest::stressTestMemoryFragmentation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KUndo2Stack undoStack; #ifdef LIMIT_LONG_TESTS const int numCycles = 3; undoStack.setUndoLimit(1); #else const int numCycles = 200; undoStack.setUndoLimit(10); #endif const int numThreads = 16; const int desiredWidth = 10000; const int patchSize = 81; const int numSidePatches = desiredWidth / patchSize; QThreadPool pool; pool.setMaxThreadCount(numThreads); for (int i = 0; i < numCycles; i++) { qDebug() << "iteration"<< i; // KisTransaction t(dev); for (int y = 0; y < numSidePatches; y++) { for (int x = 0; x < numSidePatches; x++) { const QRect workerRect(x * patchSize, y * patchSize, patchSize, patchSize); pool.start(new FillWorker(dev, workerRect, (i + x + y) & 0x1)); } } pool.waitForDone(); // undoStack.push(t.endAndTake()); qDebug() << "Iteration:" << i; #ifdef Q_OS_LINUX struct mallinfo info = mallinfo(); qDebug() << "Allocated on heap:" << (info.arena >> 20) << "MiB"; qDebug() << "Mmaped regions:" << info.hblks << (info.hblkhd >> 20) << "MiB"; qDebug() << "Free fastbin chunks:" << info.smblks << (info.fsmblks >> 10) << "KiB"; qDebug() << "Allocated in ordinary blocks" << (info.uordblks >> 20) << "MiB"; qDebug() << "Free in ordinary blockes" << info.ordblks << (info.fordblks >> 20) << "MiB"; #endif qDebug() << "========================================"; } undoStack.clear(); } KISTEST_MAIN(KisPaintDeviceTest) diff --git a/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h b/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h index f81fbea0f6..fb723d0a17 100644 --- a/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h +++ b/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h @@ -1,122 +1,132 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KOVCMULTIARCHBUILDSUPPORT_H #define __KOVCMULTIARCHBUILDSUPPORT_H #include "config-vc.h" #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #else /* HAVE_VC */ namespace Vc { enum Implementation /*: std::uint_least32_t*/ { ScalarImpl, }; class CurrentImplementation { public: static constexpr Implementation current() { return static_cast(ScalarImpl); } }; } #endif /* HAVE_VC */ #include #include #include #include template typename FactoryType::ReturnType createOptimizedClass(typename FactoryType::ParamType param) { static bool isConfigInitialized = false; static bool useVectorization = true; + static bool disableAVXOptimizations = false; if (!isConfigInitialized) { KConfigGroup cfg = KSharedConfig::openConfig()->group(""); useVectorization = !cfg.readEntry("amdDisableVectorWorkaround", false); + disableAVXOptimizations = cfg.readEntry("disableAVXOptimizations", false); isConfigInitialized = true; } if (!useVectorization) { qWarning() << "WARNING: vector instructions disabled by \'amdDisableVectorWorkaround\' option!"; return FactoryType::template create(param); } + #ifdef HAVE_VC + if (disableAVXOptimizations && + (Vc::isImplementationSupported(Vc::AVXImpl) || + Vc::isImplementationSupported(Vc::AVX2Impl))) { + + qWarning() << "WARNING: AVX and AVX2 optimizations are disabled by \'disableAVXOptimizations\' option!"; + } + /** * We use SSE2, SSSE3, SSE4.1, AVX and AVX2. * The rest are integer and string instructions mostly. * * TODO: Add FMA3/4 when it is adopted by Vc */ - if (Vc::isImplementationSupported(Vc::AVX2Impl)) { + if (!disableAVXOptimizations && Vc::isImplementationSupported(Vc::AVX2Impl)) { return FactoryType::template create(param); - } else if (Vc::isImplementationSupported(Vc::AVXImpl)) { + } else if (!disableAVXOptimizations && Vc::isImplementationSupported(Vc::AVXImpl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::SSE41Impl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::SSSE3Impl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::SSE2Impl)) { return FactoryType::template create(param); } else { #endif return FactoryType::template create(param); #ifdef HAVE_VC } #endif } template typename FactoryType::ReturnType createOptimizedClass(typename FactoryType::ParamType param, bool forceScalarImplemetation) { if(forceScalarImplemetation){ return FactoryType::template create(param); } return createOptimizedClass(param); } #endif /* __KOVCMULTIARCHBUILDSUPPORT_H */ diff --git a/libs/store/KoQuaZipStore.cpp b/libs/store/KoQuaZipStore.cpp index 29f08fbfe4..7ac1a17215 100644 --- a/libs/store/KoQuaZipStore.cpp +++ b/libs/store/KoQuaZipStore.cpp @@ -1,292 +1,292 @@ /* * Copyright (C) 2019 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoQuaZipStore.h" #include "KoStore_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct KoQuaZipStore::Private { Private() {} ~Private() {} QuaZip *archive {0}; QuaZipFile *currentFile {0}; int compressionLevel {Z_DEFAULT_COMPRESSION}; bool usingSaveFile {false}; QByteArray cache; QBuffer buffer; }; KoQuaZipStore::KoQuaZipStore(const QString &_filename, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype) : KoStore(_mode, writeMimetype) , dd(new Private()) { Q_D(KoStore); d->localFileName = _filename; dd->archive = new QuaZip(_filename); init(appIdentification); } KoQuaZipStore::KoQuaZipStore(QIODevice *dev, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype) : KoStore(_mode, writeMimetype) , dd(new Private()) { dd->archive = new QuaZip(dev); init(appIdentification); } KoQuaZipStore::~KoQuaZipStore() { Q_D(KoStore); if (dd->currentFile && dd->currentFile->isOpen()) { dd->currentFile->close(); } if (!d->finalized) { finalize(); } delete dd->archive; delete dd->currentFile; } void KoQuaZipStore::setCompressionEnabled(bool enabled) { if (enabled) { dd->compressionLevel = Z_BEST_COMPRESSION; } else { dd->compressionLevel = Z_NO_COMPRESSION; } } qint64 KoQuaZipStore::write(const char *_data, qint64 _len) { Q_D(KoStore); if (_len == 0) return 0; if (!d->isOpen) { errorStore << "KoStore: You must open before writing" << endl; return 0; } if (d->mode != Write) { errorStore << "KoStore: Can not write to store that is opened for reading" << endl; return 0; } d->size += _len; if (dd->buffer.write(_data, _len)) { // writeData returns a bool! return _len; } return 0; } QStringList KoQuaZipStore::directoryList() const { return dd->archive->getFileNameList(); } void KoQuaZipStore::init(const QByteArray &appIdentification) { Q_D(KoStore); bool enableZip64 = false; if (appIdentification == "application/x-krita") { enableZip64 = KSharedConfig::openConfig()->group("").readEntry("UseZip64", false); } dd->archive->setZip64Enabled(enableZip64); dd->archive->setFileNameCodec("UTF-8"); dd->usingSaveFile = dd->archive->getIoDevice() && dd->archive->getIoDevice()->inherits("QSaveFile"); dd->archive->setAutoClose(!dd->usingSaveFile); d->good = dd->archive->open(d->mode == Write ? QuaZip::mdCreate : QuaZip::mdUnzip); if (!d->good) { return; } if (d->mode == Write) { if (d->writeMimetype) { QuaZipFile f(dd->archive); QuaZipNewInfo newInfo("mimetype"); newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther); if (!f.open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, Z_NO_COMPRESSION)) { d->good = false; return; } f.write(appIdentification); f.close(); } } else { debugStore << dd->archive->getEntriesCount() << dd->archive->getFileNameList(); d->good = dd->archive->getEntriesCount(); } } bool KoQuaZipStore::doFinalize() { Q_D(KoStore); d->stream = 0; if (!dd->usingSaveFile) { dd->archive->close(); } return dd->archive->getZipError() == ZIP_OK; } bool KoQuaZipStore::openWrite(const QString &name) { Q_D(KoStore); QString fixedPath = name; fixedPath.replace("//", "/"); delete d->stream; d->stream = 0; // Not used when writing delete dd->currentFile; dd->currentFile = new QuaZipFile(dd->archive); QuaZipNewInfo newInfo(fixedPath); newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther); bool r = dd->currentFile->open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, dd->compressionLevel); if (!r) { qWarning() << "Could not open" << name << dd->currentFile->getZipError(); } dd->cache = QByteArray(); dd->buffer.setBuffer(&dd->cache); dd->buffer.open(QBuffer::WriteOnly); return r; } bool KoQuaZipStore::openRead(const QString &name) { Q_D(KoStore); QString fixedPath = name; fixedPath.replace("//", "/"); if (!d->substituteThis.isEmpty()) { fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith); } delete d->stream; d->stream = 0; delete dd->currentFile; dd->currentFile = 0; if (!currentPath().isEmpty() && !fixedPath.startsWith(currentPath())) { fixedPath = currentPath() + '/' + fixedPath; } if (!dd->archive->setCurrentFile(fixedPath)) { qWarning() << "\t\tCould not set current file" << dd->archive->getZipError() << fixedPath; return false; } dd->currentFile = new QuaZipFile(dd->archive); if (!dd->currentFile->open(QIODevice::ReadOnly)) { qWarning() << "\t\t\tBut could not open!!!" << dd->archive->getZipError(); return false; } d->stream = dd->currentFile; d->size = dd->currentFile->size(); return true; } bool KoQuaZipStore::closeWrite() { Q_D(KoStore); bool r = true; if (!dd->currentFile->write(dd->cache)) { qWarning() << "Could not write buffer to the file"; r = false; } dd->buffer.close(); dd->currentFile->close(); d->stream = 0; return (r && dd->currentFile->getZipError() == ZIP_OK); } bool KoQuaZipStore::closeRead() { Q_D(KoStore); d->stream = 0; return true; } -bool KoQuaZipStore::enterRelativeDirectory(const QString &path) +bool KoQuaZipStore::enterRelativeDirectory(const QString & /*path*/) { return true; } bool KoQuaZipStore::enterAbsoluteDirectory(const QString &path) { QString fixedPath = path; fixedPath.replace("//", "/"); if (fixedPath.isEmpty()) { fixedPath = "/"; } QuaZipDir currentDir (dd->archive, fixedPath); return currentDir.exists(); } bool KoQuaZipStore::fileExists(const QString &absPath) const { Q_D(const KoStore); QString fixedPath = absPath; fixedPath.replace("//", "/"); if (!d->substituteThis.isEmpty()) { fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith); } return dd->archive->getFileNameList().contains(fixedPath); } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index fccc4b5648..abc07cc467 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2615 +1,2631 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KisResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(updateWindowMenu())); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } + if (d->mdiArea) { + d->mdiArea->setPalette(qApp->palette()); + for (int i=0; imdiArea->subWindowList().size(); i++) { + QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); + if (window) { + window->setPalette(qApp->palette()); + KisView *view = qobject_cast(window->widget()); + if (view) { + view->slotThemeChanged(qApp->palette()); + } + } + } + } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; QString versionString = KritaVersionWrapper::versionString(true); #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setCaption(QString("%1: %2").arg(versionString).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResourceSP workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); + QFontMetrics fontMetrics = docMenu->fontMetrics(); + int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); + Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { - QString title = doc->url().toDisplayString(); + QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { - text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); + text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { - text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); + text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } #include diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index 7ffe97a417..17dc439ca9 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,1046 +1,1063 @@ /* * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisView.h" #include "KisView_p.h" #include #include #include #include "KoPageLayout.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_config.h" #include "KisDocument.h" #include "kis_image_manager.h" #include "KisMainWindow.h" #include "kis_mimedata.h" #include "kis_mirror_axis.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_shape_controller.h" #include "kis_tool_freehand.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" #include "KisReferenceImagesDecoration.h" #include "kis_progress_widget.h" #include "kis_signal_compressor.h" #include "kis_filter_manager.h" #include "kis_file_layer.h" #include "krita_utils.h" #include "input/kis_input_manager.h" #include "KisRemoteFileFetcher.h" #include "kis_selection_manager.h" //static QString KisView::newObjectName() { static int s_viewIFNumber = 0; QString name; name.setNum(s_viewIFNumber++); name.prepend("view_"); return name; } bool KisView::s_firstView = true; class Q_DECL_HIDDEN KisView::Private { public: Private(KisView *_q, KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection) : actionCollection(actionCollection) , viewConverter() , canvasController(_q, actionCollection) , canvas(&viewConverter, resourceManager, _q, document->shapeController()) , zoomManager(_q, &this->viewConverter, &this->canvasController) , paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q)) , referenceImagesDecoration(new KisReferenceImagesDecoration(_q, document)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } bool inOperation; //in the middle of an operation (no screen refreshing)? QPointer document; // our KisDocument QWidget *tempActiveWidget = 0; /** * Signals the document has been deleted. Can't use document==0 since this * only happens in ~QObject, and views get deleted by ~KisDocument. * XXX: either provide a better justification to do things this way, or * rework the mechanism. */ bool documentDeleted = false; KActionCollection* actionCollection; KisCoordinatesConverter viewConverter; KisCanvasController canvasController; KisCanvas2 canvas; KisZoomManager zoomManager; KisViewManager *viewManager = 0; KisNodeSP currentNode; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration; KisReferenceImagesDecorationSP referenceImagesDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; QMdiSubWindow *subWindow{nullptr}; bool softProofing = false; bool gamutCheck = false; // Hmm sorry for polluting the private class with such a big inner class. // At the beginning it was a little struct :) class StatusBarItem { public: StatusBarItem(QWidget * widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_connected(false), m_hidden(false) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void ensureItemShown(QStatusBar * sb) { Q_ASSERT(m_widget); if (!m_connected) { if (m_permanent) sb->addPermanentWidget(m_widget, m_stretch); else sb->addWidget(m_widget, m_stretch); if(!m_hidden) m_widget->show(); m_connected = true; } } void ensureItemHidden(QStatusBar * sb) { if (m_connected) { m_hidden = m_widget->isHidden(); sb->removeWidget(m_widget); m_widget->hide(); m_connected = false; } } private: QWidget * m_widget = 0; int m_stretch; bool m_permanent; bool m_connected = false; bool m_hidden = false; }; }; KisView::KisView(KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection, QWidget *parent) : QWidget(parent) , d(new Private(this, document, resourceManager, actionCollection)) { Q_ASSERT(document); connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool))); setObjectName(newObjectName()); d->document = document; setFocusPolicy(Qt::StrongFocus); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(QString,int)), this, SLOT(slotSavingStatusMessage(QString,int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg(false); d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVastScrolling(cfg.vastScrolling()); d->canvasController.setCanvas(&d->canvas); d->zoomManager.setup(d->actionCollection); connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged())); setAcceptDrops(true); connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished())); d->canvas.addDecoration(d->referenceImagesDecoration); d->referenceImagesDecoration->setVisible(true); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); d->zoomManager.updateScreenResolution(this); } KisView::~KisView() { if (d->viewManager) { if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } d->viewManager->mainWindow()->notifyChildViewDestroyed(this); } KoToolManager::instance()->removeCanvasController(&d->canvasController); d->canvasController.setCanvas(0); KisPart::instance()->removeView(this); delete d; } void KisView::notifyCurrentStateChanged(bool isCurrent) { d->isCurrent = isCurrent; if (!d->isCurrent && d->savedFloatingMessage) { d->savedFloatingMessage->removeMessage(); } KisInputManager *inputManager = globalInputManager(); if (d->isCurrent) { inputManager->attachPriorityEventFilter(&d->canvasController); } else { inputManager->detachPriorityEventFilter(&d->canvasController); } /** * When current view is changed, currently selected node is also changed, * therefore we should update selection overlay mask */ viewManager()->selectionManager()->selectionChanged(); } bool KisView::isCurrent() const { return d->isCurrent; } void KisView::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisView::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->viewManager) return; if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) { if (d->savedFloatingMessage) { d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment); } else { d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment); d->savedFloatingMessage->setShowOverParent(true); d->savedFloatingMessage->setIcon(icon); connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage())); d->floatingMessageCompressor.start(); } } } bool KisView::canvasIsMirrored() const { return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored(); } void KisView::setViewManager(KisViewManager *view) { d->viewManager = view; KoToolManager::instance()->addController(&d->canvasController); KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController); dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas); if (resourceProvider()) { resourceProvider()->slotImageSizeChanged(); } if (d->viewManager && d->viewManager->nodeManager()) { d->viewManager->nodeManager()->nodesUpdated(); } connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged(QPointF,QPointF))); connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged())); // executed in a context of an image thread connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), SLOT(slotImageNodeAdded(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)), SLOT(slotContinueAddNode(KisNodeSP)), Qt::AutoConnection); // executed in a context of an image thread connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(slotImageNodeRemoved(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)), SLOT(slotContinueRemoveNode(KisNodeSP)), Qt::AutoConnection); d->viewManager->updateGUI(); KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } KisViewManager* KisView::viewManager() const { return d->viewManager; } void KisView::slotImageNodeAdded(KisNodeSP node) { emit sigContinueAddNode(node); } void KisView::slotContinueAddNode(KisNodeSP newActiveNode) { /** * When deleting the last layer, root node got selected. We should * fix it when the first layer is added back. * * Here we basically reimplement what Qt's view/model do. But * since they are not connected, we should do it manually. */ if (!d->isCurrent && (!d->currentNode || !d->currentNode->parent())) { d->currentNode = newActiveNode; } } void KisView::slotImageNodeRemoved(KisNodeSP node) { emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node)); } void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode) { if (!d->isCurrent) { d->currentNode = newActiveNode; } } KoZoomController *KisView::zoomController() const { return d->zoomManager.zoomController(); } KisZoomManager *KisView::zoomManager() const { return &d->zoomManager; } KisCanvasController *KisView::canvasController() const { return &d->canvasController; } KisCanvasResourceProvider *KisView::resourceProvider() const { if (d->viewManager) { return d->viewManager->canvasResourceProvider(); } return 0; } KisInputManager* KisView::globalInputManager() const { return d->viewManager ? d->viewManager->inputManager() : 0; } KisCanvas2 *KisView::canvasBase() const { return &d->canvas; } KisImageWSP KisView::image() const { if (d->document) { return d->document->image(); } return 0; } KisCoordinatesConverter *KisView::viewConverter() const { return &d->viewConverter; } void KisView::dragEnterEvent(QDragEnterEvent *event) { //qDebug() << "KisView::dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node")) { event->accept(); // activate view if it should accept the drop this->setFocus(); } else { event->ignore(); } } void KisView::dropEvent(QDropEvent *event) { KisImageWSP kisimage = image(); Q_ASSERT(kisimage); QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint(); QRect imageBounds = kisimage->bounds(); QPoint pasteCenter; bool forceRecenter; if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(cursorPos)) { pasteCenter = cursorPos; forceRecenter = true; } else { pasteCenter = imageBounds.center(); forceRecenter = false; } //qDebug() << "KisView::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); if (event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasImage()) { KisShapeController *kritaShapeController = dynamic_cast(d->document->shapeController()); QList nodes = KisMimeData::loadNodes(event->mimeData(), imageBounds, pasteCenter, forceRecenter, kisimage, kritaShapeController); Q_FOREACH (KisNodeSP node, nodes) { if (node) { KisNodeCommandsAdapter adapter(viewManager()); if (!viewManager()->nodeManager()->activeLayer()) { adapter.addNode(node, kisimage->rootLayer() , 0); } else { adapter.addNode(node, viewManager()->nodeManager()->activeLayer()->parent(), viewManager()->nodeManager()->activeLayer()); } } } } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); if (urls.length() > 0) { QMenu popup; popup.setObjectName("drop_popup"); QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup); QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup); QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup); QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup); QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup); QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup); QAction *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup); QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); popup.addAction(insertAsReferenceImage); popup.addAction(insertManyLayers); popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); popup.addAction(insertAsReferenceImages); insertAsNewLayer->setEnabled(image() && urls.count() == 1); insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); insertAsReferenceImage->setEnabled(image() && urls.count() == 1); insertManyLayers->setEnabled(image() && urls.count() > 1); insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); insertAsReferenceImages->setEnabled(image() && urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; for (QUrl url : urls) { if (!url.isLocalFile()) { // download the file and substitute the url KisRemoteFileFetcher fetcher; tmp = new QTemporaryFile(); tmp->setAutoRemove(true); if (!fetcher.fetchFile(url, tmp)) { qWarning() << "Fetching" << url << "failed"; continue; } url = url.fromLocalFile(tmp->fileName()); } if (url.isLocalFile()) { if (action == insertAsNewLayer || action == insertManyLayers) { d->viewManager->imageManager()->importImage(url); activateWindow(); } else if (action == insertAsNewFileLayer || action == insertManyFileLayers) { KisNodeCommandsAdapter adapter(viewManager()); KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(), KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8); adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode()); } else if (action == openInNewDocument || action == openManyDocuments) { if (mainWindow()) { mainWindow()->openDocument(url, KisMainWindow::None); } } else if (action == insertAsReferenceImage || action == insertAsReferenceImages) { auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter, this); if (reference) { reference->setPosition(d->viewConverter.imageToDocument(cursorPos)); d->referenceImagesDecoration->addReferenceImage(reference); KoToolManager::instance()->switchToolRequested("ToolReferenceImages"); } } } delete tmp; tmp = 0; } } } } } void KisView::dragMoveEvent(QDragMoveEvent *event) { //qDebug() << "KisView::dragMoveEvent"; if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } KisDocument *KisView::document() const { return d->document; } void KisView::setDocument(KisDocument *document) { d->document->disconnect(this); d->document = document; QStatusBar *sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(QString,int)), this, SLOT(slotSavingStatusMessage(QString,int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } } void KisView::setDocumentDeleted() { d->documentDeleted = true; } QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent) { Q_UNUSED(parent); QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this); printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage()); printDialog->setEnabledOptions(printJob->printDialogOptions()); return printDialog; } KisMainWindow * KisView::mainWindow() const { return dynamic_cast(window()); } void KisView::setSubWindow(QMdiSubWindow *subWindow) { d->subWindow = subWindow; } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving) { QStatusBar *sb = statusBar(); if (sb) { sb->showMessage(text, timeout); } KisConfig cfg(true); if (!sb || sb->isHidden() || (!isAutoSaving && cfg.forceShowSaveMessages()) || (cfg.forceShowAutosaveMessages() && isAutoSaving)) { viewManager()->showFloatingMessage(text, QIcon()); } } void KisView::slotClearStatusText() { QStatusBar *sb = statusBar(); if (sb) { sb->clearMessage(); } } QList KisView::createChangeUnitActions(bool addPixelUnit) { UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this); return unitActions->actions(); } void KisView::closeEvent(QCloseEvent *event) { // Check whether we're the last (user visible) view int viewCount = KisPart::instance()->viewCount(document()); if (viewCount > 1 || !isVisible()) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { d->viewManager->statusBar()->setView(0); event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; document()->waitForSavingToComplete(); if (document()->isModified()) { QString name; if (document()->documentInfo()) { name = document()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = document()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : { bool isNative = (document()->mimeType() == document()->nativeFormatMimeType()); if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false)) return false; break; } case QMessageBox::No : { KisImageSP image = document()->image(); image->requestStrokeCancellation(); viewManager()->blockUntilOperationsFinishedForced(image); document()->removeAutoSaveFiles(document()->localFilePath(), document()->isRecovered()); document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; } default : // case QMessageBox::Cancel : return false; } } return true; } void KisView::slotScreenChanged() { d->zoomManager.updateScreenResolution(this); } +void KisView::slotThemeChanged(QPalette pal) +{ + this->setPalette(pal); + for (int i=0; ichildren().size();i++) { + QWidget *w = qobject_cast ( this->children().at(i)); + if (w) { + w->setPalette(pal); + } + } + if (canvasBase()) { + canvasBase()->canvasWidget()->setPalette(pal); + } + if (canvasController()) { + canvasController()->setPalette(pal); + } +} + void KisView::resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint, const QPointF &newImageStillPoint) { const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter(); QPointF oldPreferredCenter = d->canvasController.preferredCenter(); /** * Calculating the still point in old coordinates depending on the * parameters given */ QPointF oldStillPoint; if (changeCentering) { oldStillPoint = converter->imageToWidget(oldImageStillPoint) + converter->documentOffset(); } else { QSize oldDocumentSize = d->canvasController.documentSize(); oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height()); } /** * Updating the document size */ QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes()); KoZoomController *zc = d->zoomManager.zoomController(); zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom()); zc->setPageSize(size); zc->setDocumentSize(size, true); /** * Calculating the still point in new coordinates depending on the * parameters given */ QPointF newStillPoint; if (changeCentering) { newStillPoint = converter->imageToWidget(newImageStillPoint) + converter->documentOffset(); } else { QSize newDocumentSize = d->canvasController.documentSize(); newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height()); } d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint); } void KisView::syncLastActiveNodeToDocument() { KisDocument *doc = document(); if (doc) { doc->setPreActivatedNode(d->currentNode); } } void KisView::saveViewState(KisPropertiesConfiguration &config) const { config.setProperty("file", d->document->url()); config.setProperty("window", mainWindow()->windowStateConfig().name()); if (d->subWindow) { config.setProperty("geometry", d->subWindow->saveGeometry().toBase64()); } config.setProperty("zoomMode", (int)zoomController()->zoomMode()); config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom()); d->canvasController.saveCanvasState(config); } void KisView::restoreViewState(const KisPropertiesConfiguration &config) { if (d->subWindow) { QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1()); d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry)); } qreal zoom = config.getFloat("zoom", 1.0f); int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE); d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom); d->canvasController.restoreCanvasState(config); } void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; d->canvas.slotTrySwitchShapeManager(); syncLastActiveNodeToDocument(); } KisNodeSP KisView::currentNode() const { return d->currentNode; } KisLayerSP KisView::currentLayer() const { KisNodeSP node; KisMaskSP mask = currentMask(); if (mask) { node = mask->parent(); } else { node = d->currentNode; } return qobject_cast(node.data()); } KisMaskSP KisView::currentMask() const { return dynamic_cast(d->currentNode.data()); } KisSelectionSP KisView::selection() { KisLayerSP layer = currentLayer(); if (layer) return layer->selection(); // falls through to the global // selection, or 0 in the end if (image()) { return image()->globalSelection(); } return 0; } void KisView::slotSoftProofing(bool softProofing) { d->softProofing = softProofing; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Soft Proofing doesn't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (softProofing){ message = i18n("Soft Proofing turned on."); } else { message = i18n("Soft Proofing turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotSoftProofing(softProofing); } void KisView::slotGamutCheck(bool gamutCheck) { d->gamutCheck = gamutCheck; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Gamut Warnings don't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (gamutCheck){ message = i18n("Gamut Warnings turned on."); if (!d->softProofing){ message += "\n "+i18n("But Soft Proofing is still off."); } } else { message = i18n("Gamut Warnings turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotGamutCheck(gamutCheck); } bool KisView::softProofing() { return d->softProofing; } bool KisView::gamutCheck() { return d->gamutCheck; } void KisView::slotLoadingFinished() { if (!document()) return; /** * Cold-start of image size/resolution signals */ slotImageResolutionChanged(); if (image()->locked()) { // If this is the first view on the image, the image will have been locked // so unlock it. image()->blockSignals(false); image()->unlock(); } canvasBase()->initializeImage(); /** * Dirty hack alert */ d->zoomManager.zoomController()->setAspectMode(true); if (viewConverter()) { viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE); } connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*))); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF))); KisNodeSP activeNode = document()->preActivatedNode(); if (!activeNode) { activeNode = image()->rootLayer()->lastChild(); } while (activeNode && !activeNode->inherits("KisLayer")) { activeNode = activeNode->prevSibling(); } setCurrentNode(activeNode); connect(d->viewManager->mainWindow(), SIGNAL(screenChanged()), SLOT(slotScreenChanged())); zoomManager()->updateImageBoundsSnapping(); } void KisView::slotSavingFinished() { if (d->viewManager && d->viewManager->mainWindow()) { d->viewManager->mainWindow()->updateCaption(); } } KisPrintJob * KisView::createPrintJob() { return new KisPrintJob(image()); } void KisView::slotImageResolutionChanged() { resetImageSizeAndScroll(false); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); // update KoUnit value for the document if (resourceProvider()) { resourceProvider()->resourceManager()-> setResource(KoCanvasResourceProvider::Unit, d->canvas.unit()); } } void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint) { resetImageSizeAndScroll(true, oldStillPoint, newStillPoint); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); } void KisView::closeView() { d->subWindow->close(); } diff --git a/libs/ui/KisView.h b/libs/ui/KisView.h index bc90f1e32e..9f2a67421e 100644 --- a/libs/ui/KisView.h +++ b/libs/ui/KisView.h @@ -1,300 +1,302 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2007 Thomas Zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VIEW_H #define KIS_VIEW_H #include #include #include #include #include "kritaui_export.h" #include "widgets/kis_floating_message.h" class KisDocument; class KisMainWindow; class KisPrintJob; class KisCanvasController; class KisZoomManager; class KisCanvas2; class KisViewManager; class KisDocument; class KisCanvasResourceProvider; class KisCoordinatesConverter; class KisInputManager; class KoZoomController; class KoZoomController; struct KoPageLayout; class KoCanvasResourceProvider; // KDE classes class QAction; class KActionCollection; class KConfigGroup; // Qt classes class QDragEnterEvent; class QDragMoveEvent; class QDropEvent; class QPrintDialog; class QCloseEvent; class QStatusBar; class QMdiSubWindow; /** * This class is used to display a @ref KisDocument. * * Multiple views can be attached to one document at a time. */ class KRITAUI_EXPORT KisView : public QWidget { Q_OBJECT public: /** * Creates a new view for the document. */ KisView(KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection, QWidget *parent = 0); ~KisView() override; // Temporary while teasing apart view and mainwindow void setViewManager(KisViewManager *view); KisViewManager *viewManager() const; public: /** * Retrieves the document object of this view. */ KisDocument *document() const; /** * Reset the view to show the given document. */ void setDocument(KisDocument *document); /** * Tells this view that its document has got deleted (called internally) */ void setDocumentDeleted(); /** * In order to print the document represented by this view a new print job should * be constructed that is capable of doing the printing. * The default implementation returns 0, which silently cancels printing. */ KisPrintJob * createPrintJob(); /** * Create a QPrintDialog based on the @p printJob */ QPrintDialog *createPrintDialog(KisPrintJob *printJob, QWidget *parent); /** * @return the KisMainWindow in which this view is currently. */ KisMainWindow *mainWindow() const; /** * Tells this view which subwindow it is part of. */ void setSubWindow(QMdiSubWindow *subWindow); /** * @return the statusbar of the KisMainWindow in which this view is currently. */ QStatusBar *statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); /** * Return the zoomController for this view. */ KoZoomController *zoomController() const; /// create a list of actions that when activated will change the unit on the document. QList createChangeUnitActions(bool addPixelUnit = false); void closeView(); public: /** * The zoommanager handles everything action-related to zooming */ KisZoomManager *zoomManager() const; /** * The CanvasController decorates the canvas with scrollbars * and knows where to start painting on the canvas widget, i.e., * the document offset. */ KisCanvasController *canvasController() const; KisCanvasResourceProvider *resourceProvider() const; /** * Filters events and sends them to canvas actions. Shared * among all the views/canvases * * NOTE: May be null while initialization! */ KisInputManager* globalInputManager() const; /** * @return the canvas object */ KisCanvas2 *canvasBase() const; /// @return the image this view is displaying KisImageWSP image() const; KisCoordinatesConverter *viewConverter() const; void resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint = QPointF(), const QPointF &newImageStillPoint = QPointF()); void setCurrentNode(KisNodeSP node); KisNodeSP currentNode() const; KisLayerSP currentLayer() const; KisMaskSP currentMask() const; /** * @brief softProofing * @return whether or not we're softproofing in this view. */ bool softProofing(); /** * @brief gamutCheck * @return whether or not we're using gamut warnings in this view. */ bool gamutCheck(); /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); void notifyCurrentStateChanged(bool isCurrent); bool isCurrent() const; void setShowFloatingMessage(bool show); void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500, KisFloatingMessage::Priority priority = KisFloatingMessage::Medium, int alignment = Qt::AlignCenter | Qt::TextWordWrap); bool canvasIsMirrored() const; void syncLastActiveNodeToDocument(); void saveViewState(KisPropertiesConfiguration &config) const; void restoreViewState(const KisPropertiesConfiguration &config); public Q_SLOTS: /** * Display a message in the status bar (calls QStatusBar::message()) * @todo rename to something more generic * @param value determines autosaving */ void slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving = false); /** * End of the message in the status bar (calls QStatusBar::clear()) * @todo rename to something more generic */ void slotClearStatusText(); /** * @brief slotSoftProofing set whether or not we're softproofing in this view. * Will be setting the same in the canvas belonging to the view. */ void slotSoftProofing(bool softProofing); /** * @brief slotGamutCheck set whether or not we're gamutchecking in this view. * Will be setting the same in the vans belonging to the view. */ void slotGamutCheck(bool gamutCheck); bool queryClose(); void slotScreenChanged(); + void slotThemeChanged(QPalette pal); + private Q_SLOTS: void slotImageNodeAdded(KisNodeSP node); void slotContinueAddNode(KisNodeSP newActiveNode); void slotImageNodeRemoved(KisNodeSP node); void slotContinueRemoveNode(KisNodeSP newActiveNode); Q_SIGNALS: // From KisImage void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void titleModified(QString,bool); void sigContinueAddNode(KisNodeSP newActiveNode); void sigContinueRemoveNode(KisNodeSP newActiveNode); protected: // QWidget overrides void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void closeEvent(QCloseEvent *event) override; /** * Generate a name for this view. */ QString newObjectName(); public Q_SLOTS: void slotLoadingFinished(); void slotSavingFinished(); void slotImageResolutionChanged(); void slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); private: class Private; Private * const d; static bool s_firstView; }; #endif diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index a07887e2f3..c61bad1e78 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1674 +1,1685 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoID.h" #include #include #include #include #include #include #include #include "KisProofingConfiguration.h" #include "KoColorConversionTransformation.h" #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "kis_action_registry.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_color_manager.h" #include "kis_config.h" #include "kis_cursor.h" #include "kis_image_config.h" #include "kis_preference_set_registry.h" #include "widgets/kis_cmb_idlist.h" #include #include "kis_file_name_requester.h" #include #include #include #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" # ifndef USE_QT_TABLET_WINDOWS # include # endif #endif struct BackupSuffixValidator : public QValidator { BackupSuffixValidator(QObject *parent) : QValidator(parent) , invalidCharacters(QStringList() << "0" << "1" << "2" << "3" << "4" << "5" << "6" << "7" << "8" << "9" << "/" << "\\" << ":" << ";" << " ") {} ~BackupSuffixValidator() override {} const QStringList invalidCharacters; State validate(QString &line, int &/*pos*/) const override { Q_FOREACH(const QString invalidChar, invalidCharacters) { if (line.contains(invalidChar)) { return Invalid; } } return Acceptable; } }; GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg(true); // // Cursor Tab // m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); // // Window Tab // m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", true).toBool()); chkUsageLogging->setChecked(kritarc.value("LogUsage", true).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); cmbFlowMode->setCurrentIndex((int)!cfg.readEntry("useCreamyAlphaDarken", true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste()); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled()); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Middle-Click Drag")); //m_cmbKineticScrollingGesture->addItem(i18n("On Right Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivitySlider->setRange(0, 100); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars()); // // File handling // int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); chkHideAutosaveFiles->setChecked(cfg.readEntry("autosavefileshidden", true)); m_chkCompressKra->setChecked(cfg.compressKra()); chkZip64->setChecked(cfg.useZip64()); m_backupFileCheckBox->setChecked(cfg.backupFile()); cmbBackupFileLocation->setCurrentIndex(cfg.readEntry("backupfilelocation", 0)); txtBackupFileSuffix->setText(cfg.readEntry("backupfilesuffix", "~")); QValidator *validator = new BackupSuffixValidator(txtBackupFileSuffix); txtBackupFileSuffix->setValidator(validator); intNumBackupFiles->setValue(cfg.readEntry("numberofbackupfiles", 1)); // // Miscellaneous // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); // // Resources // m_urlCacheDbLocation->setMode(KoFileDialog::OpenDirectory); m_urlCacheDbLocation->setConfigurationName("cachedb_location"); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setMode(KoFileDialog::OpenDirectory); m_urlResourceFolder->setConfigurationName("resource_directory"); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } void GeneralTab::setDefault() { KisConfig cfg(true); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); chkHideAutosaveFiles->setChecked(true); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); cmbBackupFileLocation->setCurrentIndex(0); txtBackupFileSuffix->setText("~"); intNumBackupFiles->setValue(1); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); chkZip64->setChecked(cfg.useZip64(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); chkUsageLogging->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); cmbFlowMode->setCurrentIndex(0); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::useZip64() { return chkZip64->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::kineticScrollingEnabled() { return m_groupBoxKineticScrollingSettings->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivitySlider->value(); } bool GeneralTab::kineticScrollingHiddenScrollbars() { return m_chkKineticScrollingHideScrollbars->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; KisSqueezedComboBox *cmb = new KisSqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors()); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg(true); refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { KisConfig cfg(true); if (useSystemProfile) { QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA")); KisConfig cfg(true); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { KisConfig cfg(true); m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #else m_page->grpTabletApi->setVisible(false); #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { return KisImageConfig(true).totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg(true); const double totalRAM = cfg.totalRAM(); lblTotalMemory->setText(KFormat().formatByteSize(totalRAM * 1024 * 1024, 0, KFormat::IECBinaryDialect, KFormat::UnitMegaByte)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 100); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); intCachedFramesSizeLimit->setRange(1, 10000); intCachedFramesSizeLimit->setSuffix(i18n(" px")); intCachedFramesSizeLimit->setSingleStep(1); intCachedFramesSizeLimit->setPageStep(1000); intRegionOfInterestMargin->setRange(1, 100); intRegionOfInterestMargin->setSuffix(i18n(" %")); intRegionOfInterestMargin->setSingleStep(1); intRegionOfInterestMargin->setPageStep(10); connect(chkCachedFramesSizeLimit, SIGNAL(toggled(bool)), intCachedFramesSizeLimit, SLOT(setEnabled(bool))); connect(chkUseRegionOfInterest, SIGNAL(toggled(bool)), intRegionOfInterestMargin, SLOT(setEnabled(bool))); +#ifndef Q_OS_WIN + // AVX workaround is needed on Windows+GCC only + chkDisableAVXOptimizations->setVisible(false); +#endif + load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg(true); sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2(true); chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); +#ifdef Q_OS_WIN + chkDisableAVXOptimizations->setChecked(cfg2.disableAVXOptimizations(requestDefault)); +#endif chkBackgroundCacheGeneration->setChecked(cfg2.calculateAnimationCacheInBackground(requestDefault)); } if (cfg.useOnDiskAnimationCacheSwapping(requestDefault)) { optOnDisk->setChecked(true); } else { optInMemory->setChecked(true); } chkCachedFramesSizeLimit->setChecked(cfg.useAnimationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setValue(cfg.animationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setEnabled(chkCachedFramesSizeLimit->isChecked()); chkUseRegionOfInterest->setChecked(cfg.useAnimationCacheRegionOfInterest(requestDefault)); intRegionOfInterestMargin->setValue(cfg.animationCacheRegionOfInterestMargin(requestDefault) * 100.0); intRegionOfInterestMargin->setEnabled(chkUseRegionOfInterest->isChecked()); } void PerformanceTab::save() { KisImageConfig cfg(false); cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2(true); cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); +#ifdef Q_OS_WIN + cfg2.setDisableAVXOptimizations(chkDisableAVXOptimizations->isChecked()); +#endif cfg2.setCalculateAnimationCacheInBackground(chkBackgroundCacheGeneration->isChecked()); } cfg.setUseOnDiskAnimationCacheSwapping(optOnDisk->isChecked()); cfg.setUseAnimationCacheFrameSizeLimit(chkCachedFramesSizeLimit->isChecked()); cfg.setAnimationCacheFrameSizeLimit(intCachedFramesSizeLimit->value()); cfg.setUseAnimationCacheRegionOfInterest(chkUseRegionOfInterest->isChecked()); cfg.setAnimationCacheRegionOfInterestMargin(intRegionOfInterestMargin->value() / 100.0); } void PerformanceTab::selectSwapDir() { KisImageConfig cfg(true); QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" #include "opengl/KisOpenGLModeProber.h" #include "opengl/KisScreenInformationAdapter.h" #include #include QString colorSpaceString(KisSurfaceColorSpace cs, int depth) { const QString csString = #ifdef HAVE_HDR cs == KisSurfaceColorSpace::bt2020PQColorSpace ? "Rec. 2020 PQ" : cs == KisSurfaceColorSpace::scRGBColorSpace ? "Rec. 709 Linear" : #endif cs == KisSurfaceColorSpace::sRGBColorSpace ? "sRGB" : cs == KisSurfaceColorSpace::DefaultColorSpace ? "sRGB" : "Unknown Color Space"; return QString("%1 (%2 bit)").arg(csString).arg(depth); } int formatToIndex(KisConfig::RootSurfaceFormat fmt) { return fmt == KisConfig::BT2020_PQ ? 1 : fmt == KisConfig::BT709_G10 ? 2 : 0; } KisConfig::RootSurfaceFormat indexToFormat(int value) { return value == 1 ? KisConfig::BT2020_PQ : value == 2 ? KisConfig::BT709_G10 : KisConfig::BT709_G22; } DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); #ifdef Q_OS_WIN const QString rendererOpenGLESText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #else const QString rendererOpenGLESText = i18nc("canvas renderer", "OpenGL ES"); #endif lblCurrentRenderer->setText(KisOpenGL::hasOpenGLES() ? rendererOpenGLESText : rendererOpenGLText); cmbPreferredRenderer->clear(); QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { qtPreferredRendererText = rendererOpenGLESText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbPreferredRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbPreferredRenderer->setCurrentIndex(0); if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #ifdef Q_OS_WIN if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->addItem(rendererOpenGLESText, KisOpenGL::RendererOpenGLES); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #endif if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } lblCurrentDisplayFormat->setText(""); lblCurrentRootSurfaceFormat->setText(""); lblHDRWarning->setText(""); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::sRGBColorSpace, 8)); #ifdef HAVE_HDR cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::bt2020PQColorSpace, 10)); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::scRGBColorSpace, 16)); #endif cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); QOpenGLContext *context = QOpenGLContext::currentContext(); if (!context) { context = QOpenGLContext::globalShareContext(); } if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (screen && adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { QStringList toolTip; toolTip << i18n("Display Id: %1", info.screen->name()); toolTip << i18n("Display Name: %1 %2", info.screen->manufacturer(), info.screen->model()); toolTip << i18n("Min Luminance: %1", info.minLuminance); toolTip << i18n("Max Luminance: %1", info.maxLuminance); toolTip << i18n("Max Full Frame Luminance: %1", info.maxFullFrameLuminance); toolTip << i18n("Red Primary: %1, %2", info.redPrimary[0], info.redPrimary[1]); toolTip << i18n("Green Primary: %1, %2", info.greenPrimary[0], info.greenPrimary[1]); toolTip << i18n("Blue Primary: %1, %2", info.bluePrimary[0], info.bluePrimary[1]); toolTip << i18n("White Point: %1, %2", info.whitePoint[0], info.whitePoint[1]); lblCurrentDisplayFormat->setToolTip(toolTip.join('\n')); lblCurrentDisplayFormat->setText(colorSpaceString(info.colorSpace, info.bitsPerColor)); } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); } } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); qWarning() << "Failed to fetch display info:" << adapter.errorString(); } const QSurfaceFormat currentFormat = KisOpenGLModeProber::instance()->surfaceformatInUse(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace colorSpace = currentFormat.colorSpace(); #else KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif lblCurrentRootSurfaceFormat->setText(colorSpaceString(colorSpace, currentFormat.redBufferSize())); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(cfg.rootSurfaceFormat())); connect(cmbPreferedRootSurfaceFormat, SIGNAL(currentIndexChanged(int)), SLOT(slotPreferredSurfaceFormatChanged(int))); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } #ifndef HAVE_HDR grpHDRSettings->setVisible(false); #endif const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("
    "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
  • "); text.append(warning.toHtmlEscaped()); text.append("
  • "); } text.append("
"); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg(true); cmbPreferredRenderer->setCurrentIndex(0); if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } void DisplaySettingsTab::slotPreferredSurfaceFormatChanged(int index) { Q_UNUSED(index); QOpenGLContext *context = QOpenGLContext::currentContext(); if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { if (cmbPreferedRootSurfaceFormat->currentIndex() != formatToIndex(KisConfig::BT709_G22) && info.colorSpace == KisSurfaceColorSpace::sRGBColorSpace) { lblHDRWarning->setText(i18n("WARNING: current display doesn't support HDR rendering")); } else { lblHDRWarning->setText(""); } } } } } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); setFaceType(KPageDialog::Tree); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); m_pages << page; addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); m_pages << page; addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); m_pages << page; // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); m_pages << page; addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); m_pages << page; addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); m_pages << page; addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); m_pages << page; addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); m_pages << page; addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); m_pages << page; QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(i18nc("@action:button", "Restore Defaults")); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); KisConfig cfg(true); QString currentPageName = cfg.readEntry("KisDlgPreferences/CurrentPage"); Q_FOREACH(KPageWidgetItem *page, m_pages) { if (page->objectName() == currentPageName) { setCurrentPage(page); break; } } } KisDlgPreferences::~KisDlgPreferences() { KisConfig cfg(true); cfg.writeEntry("KisDlgPreferences/CurrentPage", currentPage()->objectName()); } void KisDlgPreferences::showEvent(QShowEvent *event){ KPageDialog::showEvent(event); button(QDialogButtonBox::Cancel)->setAutoDefault(false); button(QDialogButtonBox::Ok)->setAutoDefault(false); button(QDialogButtonBox::RestoreDefaults)->setAutoDefault(false); button(QDialogButtonBox::Cancel)->setDefault(false); button(QDialogButtonBox::Ok)->setDefault(false); button(QDialogButtonBox::RestoreDefaults)->setDefault(false); } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg(false); cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setForceAlwaysFullSizedOutline(!dialog->m_general->m_changeBrushOutline->isChecked()); cfg.setSessionOnStartup(dialog->m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(dialog->m_general->saveSessionOnQuit()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.writeEntry("autosavefileshidden", dialog->m_general->chkHideAutosaveFiles->isChecked()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.writeEntry("backupfilelocation", dialog->m_general->cmbBackupFileLocation->currentIndex()); cfg.writeEntry("backupfilesuffix", dialog->m_general->txtBackupFileSuffix->text()); cfg.writeEntry("numberofbackupfiles", dialog->m_general->intNumBackupFiles->value()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); cfg.setUseZip64(dialog->m_general->useZip64()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); kritarc.setValue("LogUsage", dialog->m_general->chkUsageLogging->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.writeEntry("useCreamyAlphaDarken", (bool)!dialog->m_general->cmbFlowMode->currentIndex()); cfg.setKineticScrollingEnabled(dialog->m_general->kineticScrollingEnabled()); cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingHideScrollbars(dialog->m_general->kineticScrollingHiddenScrollbars()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setActivateTransformToolAfterPaste(dialog->m_general->chkEnableTranformToolAfterPaste->isChecked()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); cfg.writeEntry(KisResourceCacheDb::dbLocationKey, dialog->m_general->m_urlCacheDbLocation->fileName()); cfg.writeEntry(KisResourceLocator::resourceLocationKey, dialog->m_general->m_urlResourceFolder->fileName()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(false); cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setForcePaletteColors(dialog->m_colorSettings->m_page->chkForcePaletteColor->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbPreferredRenderer->itemData( dialog->m_displaySettings->cmbPreferredRenderer->currentIndex()).toInt()); KisOpenGL::setUserPreferredOpenGLRendererConfig(renderer); } if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setRootSurfaceFormat(&kritarc, indexToFormat(dialog->m_displaySettings->cmbPreferedRootSurfaceFormat->currentIndex())); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfgImage.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/forms/wdgperformancesettings.ui b/libs/ui/forms/wdgperformancesettings.ui index b7010ef233..fe36ac054d 100644 --- a/libs/ui/forms/wdgperformancesettings.ui +++ b/libs/ui/forms/wdgperformancesettings.ui @@ -1,543 +1,550 @@ WdgPerformanceSettings 0 0 505 446 75 true Note: Krita will need to be restarted for changes to take effect 0 General RAM Memory available: 0 0 XXX MiB Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Krita will not use more memory than this limit. Memory Limit: 0 0 Krita will not use more memory than this limit. MiB Internal Pool: 0 0 MiB When undo information reaches this limit, it will be stored in a temporary file and memory will be freed. Undo will be slower. Swap Undo After: 0 0 When undo information reaches this limit, it will be stored in a temporary file and memory will be freed. Undo will be slower. MiB Swap File Size 6 The swap file will not be bigger than this limit. File Size Limit: 0 0 The swap file will not be bigger than this limit. GiB Swap File Location: 0 0 QFrame::Box TextLabel Select the location where Krita writes its swap files. ... Qt::Vertical 20 5 Advanced Multithreading CPU Limit: 0 0 <html><head/><body><p>Krita will not use more CPU cores than selected by this limit</p></body></html> Frame Rendering Clones Limit 0 0 <html><head/><body><p>When rendering animation frames (into files or during animation cache regeneration), Krita will make the specified number of copies of your image and will work on them in parallel. Each copy will demand more RAM for its storage (about 20% of the size of you image), so raise this limit only if you have enough RAM installed.</p><p><br/></p><p><span style=" font-weight:600;">Recommended value:</span> set Clones Limit to the number of <span style=" text-decoration: underline;">physical</span> (non-hyperthreaded) cores your CPU has</p></body></html> Limit frames per second while painting: 0 0 <html><head/><body><p>Krita will try to limit the number of screen updates per second to the given number. A lower number will decrease visual responsiveness but increase stylus precision on some systems like macOS.<p></body></html> Debug logging of OpenGL framerate Debug logging for brush rendering speed + + + + Disable AVX vector optimizations + + + - Disable vector optimizations (for AMD CPUs) + Disable all vector optimizations (for AMD CPUs) Progress reporting (might affect performance) Performance logging QFrame::NoFrame <html><head/><body><p>When performance logging is enabled Krita saves timing information into the '&lt;working_dir&gt;/log' folder. If you experience performance problems and want to help us, enable this option and add the contents of the directory to a bug report.</p></body></html> true Qt::Vertical 20 5 Animation Cache Cache Storage Backend <html><head/><body><p>Animation frame cache will be stored in RAM completely without any limitations</p><p><span style=" font-weight:600;">WARNING:</span> please make sure your computer has enough RAM <span style=" text-decoration: underline;">above</span> the amount you requested in General tab. Otherwise you might face system freezes.</p><p>* for 1 second of FullHD@25fps video you need extra 200 MiB of memory</p><p>* for 1 second of 4K UltraHD@25fps video you need extra 800 MiB of memory</p></body></html> In-memory <html><head/><body><p>Animation frames are stored on hard disk in the same folder as swap file. The cache is stored in a compressed way. Little amount of extra RAM is needed.</p><p>Since data transfer speed of the hard drive is low, you might want to limit cached frame size to be able to play your video at 25 fps. The limit of 2500 px is usually a good choice.</p></body></html> On-disk Cache Generation Options <html><head/><body><p>Render scaled down version of the frame if the image is bigger than the provided limit. Make sure you enable this option when using on-disk storage backend.</p></body></html> Limit cached frame size: 0 0 <html><head/><body><p>Size limit after which the frames will be scaled down</p><p><span style=" font-weight:600;">Recommended value:</span> 2500&nbsp;px</p></body></html> <html><head/><body><p>When the image is too big, render only currently visible part of it</p></body></html> Use region of interest 0 0 <html><head/><body><p>Add extra area to the region of interest to each side of the canvas.</p><p><span style=" font-weight:600;">Recommended value:</span> 25%. The region of interest will be extended by 25% to each side.</p></body></html> <html><head/><body><p>Automatically prerender animation cache in background when the user is idle</p></body></html> Enable background cache generation Qt::Vertical 20 5 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/libs/ui/input/wintab/drawpile_tablettester/tablettester.cpp b/libs/ui/input/wintab/drawpile_tablettester/tablettester.cpp index a86fe0b640..5a4c566436 100644 --- a/libs/ui/input/wintab/drawpile_tablettester/tablettester.cpp +++ b/libs/ui/input/wintab/drawpile_tablettester/tablettester.cpp @@ -1,66 +1,66 @@ /* Drawpile - a collaborative drawing program. Copyright (C) 2017 Calle Laakkonen Drawpile is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Drawpile is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Drawpile. If not, see . */ #include "tablettester.h" #include "tablettest.h" #include "ui_tablettest.h" -TabletTestDialog::TabletTestDialog( QWidget *parent) : - KoDialog(parent, Qt::Window) +TabletTestDialog::TabletTestDialog(QWidget *parent) + : KoDialog(parent, Qt::Dialog) { setCaption(i18n("Tablet Tester")); QWidget *page = new QWidget(this); m_ui = new Ui_TabletTest; m_ui->setupUi(page); setMainWidget(page); setButtons(KoDialog::Close); qApp->installEventFilter(this); } TabletTestDialog::~TabletTestDialog() { qApp->removeEventFilter(this); delete m_ui; } bool TabletTestDialog::eventFilter(QObject *watched, QEvent *e) { Q_UNUSED(watched); if(e->type() == QEvent::TabletEnterProximity || e->type() == QEvent::TabletLeaveProximity) { QTabletEvent *te = static_cast(e); bool isEraser = te->pointerType() == QTabletEvent::Eraser; bool isNear = e->type() == QEvent::TabletEnterProximity; QString msg; if(isEraser) { if (isNear) { msg = QStringLiteral("Eraser brought near"); } else { msg = QStringLiteral("Eraser taken away"); } } else { if (isNear) { msg = QStringLiteral("Pen tip brought near"); } else { msg = QStringLiteral("Pen tip taken away"); } } m_ui->logView->appendPlainText(msg); } return QDialog::eventFilter(watched, e); } diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 8fea29f793..fde02edcba 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2124 +1,2134 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" #endif KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp && qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const { return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0); } void KisConfig::setPreferredVectorImportResolutionPPI(int value) const { m_cfg.writeEntry("preferredVectorImportResolution", value); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col)); } void KisConfig::setMDIBackgroundColor(const QColor &v) const { m_cfg.writeEntry("mdiBackgroundColor", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg(true); QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::forcePaletteColors(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false)); } void KisConfig::setForcePaletteColors(bool forcePaletteColors) { m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString cs = canvasState(); #ifdef Q_OS_WIN return (m_cfg.readEntry("useOpenGLWindows", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #else return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #endif } void KisConfig::setUseOpenGL(bool useOpenGL) const { #ifdef Q_OS_WIN m_cfg.writeEntry("useOpenGLWindows", useOpenGL); #else m_cfg.writeEntry("useOpenGL", useOpenGL); #endif } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return useWin8PointerInputNoApp(&kritarc, defaultValue); #else return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #endif #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setUseWin8PointerInputNoApp(&kritarc, value); #else m_cfg.writeEntry("useWin8PointerInput", value); #endif } #else Q_UNUSED(value) #endif } bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue) { return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool(); } void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value) { settings->setValue("useWin8PointerInput", value); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); const QString xmlData = exportConfigurationXML(filterId, defaultValue); cfg->fromXML(xmlData); return cfg; } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const { KisOcioConfiguration cfg; if (!defaultValue) { cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0); cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()); cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()); cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString()); cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString()); cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString()); cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString()); } return cfg; } void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg) { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode); m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath); m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath); m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace); m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice); m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView); m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } int KisConfig::layerThumbnailSize(bool defaultValue) const { return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20)); } void KisConfig::setLayerThumbnailSize(int size) { m_cfg.writeEntry("layerThumbnailSize", size); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } bool KisConfig::kineticScrollingEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true)); } void KisConfig::setKineticScrollingEnabled(bool value) { m_cfg.writeEntry("KineticScrollingEnabled", value); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2)); } void KisConfig::setKineticScrollingGesture(int gesture) { m_cfg.writeEntry("KineticScrollingGesture", gesture); } int KisConfig::kineticScrollingSensitivity(bool defaultValue) const { return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); } void KisConfig::setKineticScrollingSensitivity(int sensitivity) { m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); } bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false)); } void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar) { m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } +void KisConfig::setDisableAVXOptimizations(bool value) +{ + m_cfg.writeEntry("disableAVXOptimizations", value); +} + +bool KisConfig::disableAVXOptimizations(bool defaultValue) const +{ + return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", false)); +} + void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } QColor KisConfig::defaultAssistantsColor(bool defaultValue) const { static const QColor defaultColor = QColor(176, 176, 176, 255); return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor); } void KisConfig::setDefaultAssistantsColor(const QColor &color) const { m_cfg.writeEntry("defaultAssistantsColor", color); } bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false); } void KisConfig::setAutoSmoothBezierCurves(bool value) { m_cfg.writeEntry("autoSmoothBezierCurves", value); } bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false); } void KisConfig::setActivateTransformToolAfterPaste(bool value) { m_cfg.writeEntry("activateTransformToolAfterPaste", value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return rootSurfaceFormat(&kritarc, defaultValue); } void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setRootSurfaceFormat(&kritarc, value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue) { QString textValue = "bt709-g22"; if (!defaultValue) { textValue = displayrc->value("rootSurfaceFormat", textValue).toString(); } return textValue == "bt709-g10" ? BT709_G10 : textValue == "bt2020-pq" ? BT2020_PQ : BT709_G22; } void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value) { const QString textValue = value == BT709_G10 ? "bt709-g10" : value == BT2020_PQ ? "bt2020-pq" : "bt709-g22"; displayrc->setValue("rootSurfaceFormat", textValue); } bool KisConfig::useZip64(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("UseZip64", false); } void KisConfig::setUseZip64(bool value) { m_cfg.writeEntry("UseZip64", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h index 62248dfaa7..7002be8a27 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,630 +1,633 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CONFIG_H_ #define KIS_CONFIG_H_ #include #include #include #include #include #include #include #include #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class QSettings; class KisOcioConfiguration; class KRITAUI_EXPORT KisConfig { public: /** * @brief KisConfig create a kisconfig object * @param readOnly if true, there will be no call to sync when the object is deleted. * Any KisConfig object created in a thread must be read-only. */ KisConfig(bool readOnly); ~KisConfig(); bool disableTouchOnCanvas(bool defaultValue = false) const; void setDisableTouchOnCanvas(bool value) const; bool useProjections(bool defaultValue = false) const; void setUseProjections(bool useProj) const; bool undoEnabled(bool defaultValue = false) const; void setUndoEnabled(bool undo) const; int undoStackLimit(bool defaultValue = false) const; void setUndoStackLimit(int limit) const; bool useCumulativeUndoRedo(bool defaultValue = false) const; void setCumulativeUndoRedo(bool value); double stackT1(bool defaultValue = false) const; void setStackT1(int T1); double stackT2(bool defaultValue = false) const; void setStackT2(int T2); int stackN(bool defaultValue = false) const; void setStackN(int N); qint32 defImageWidth(bool defaultValue = false) const; void defImageWidth(qint32 width) const; qint32 defImageHeight(bool defaultValue = false) const; void defImageHeight(qint32 height) const; qreal defImageResolution(bool defaultValue = false) const; void defImageResolution(qreal res) const; int preferredVectorImportResolutionPPI(bool defaultValue = false) const; void setPreferredVectorImportResolutionPPI(int value) const; /** * @return the id of the default color model used for creating new images. */ QString defColorModel(bool defaultValue = false) const; /** * set the id of the default color model used for creating new images. */ void defColorModel(const QString & model) const; /** * @return the id of the default color depth used for creating new images. */ QString defaultColorDepth(bool defaultValue = false) const; /** * set the id of the default color depth used for creating new images. */ void setDefaultColorDepth(const QString & depth) const; /** * @return the id of the default color profile used for creating new images. */ QString defColorProfile(bool defaultValue = false) const; /** * set the id of the default color profile used for creating new images. */ void defColorProfile(const QString & depth) const; CursorStyle newCursorStyle(bool defaultValue = false) const; void setNewCursorStyle(CursorStyle style); QColor getCursorMainColor(bool defaultValue = false) const; void setCursorMainColor(const QColor& v) const; OutlineStyle newOutlineStyle(bool defaultValue = false) const; void setNewOutlineStyle(OutlineStyle style); QRect colorPreviewRect() const; void setColorPreviewRect(const QRect &rect); /// get the profile the user has selected for the given screen QString monitorProfile(int screen) const; void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const; QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const; void setMonitorForScreen(int screen, const QString& monitor); /// Get the actual profile to be used for the given screen, which is /// either the screen profile set by the color management system or /// the custom monitor profile set by the user, depending on the configuration const KoColorProfile *displayProfile(int screen) const; QString workingColorSpace(bool defaultValue = false) const; void setWorkingColorSpace(const QString & workingColorSpace) const; QString importProfile(bool defaultValue = false) const; void setImportProfile(const QString & importProfile) const; QString printerColorSpace(bool defaultValue = false) const; void setPrinterColorSpace(const QString & printerColorSpace) const; QString printerProfile(bool defaultValue = false) const; void setPrinterProfile(const QString & printerProfile) const; bool useBlackPointCompensation(bool defaultValue = false) const; void setUseBlackPointCompensation(bool useBlackPointCompensation) const; bool allowLCMSOptimization(bool defaultValue = false) const; void setAllowLCMSOptimization(bool allowLCMSOptimization); bool forcePaletteColors(bool defaultValue = false) const; void setForcePaletteColors(bool forcePaletteColors); void writeKoColor(const QString& name, const KoColor& color) const; KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const; bool showRulers(bool defaultValue = false) const; void setShowRulers(bool rulers) const; bool forceShowSaveMessages(bool defaultValue = true) const; void setForceShowSaveMessages(bool value) const; bool forceShowAutosaveMessages(bool defaultValue = true) const; void setForceShowAutosaveMessages(bool ShowAutosaveMessages) const; bool rulersTrackMouse(bool defaultValue = false) const; void setRulersTrackMouse(bool value) const; qint32 pasteBehaviour(bool defaultValue = false) const; void setPasteBehaviour(qint32 behaviour) const; qint32 monitorRenderIntent(bool defaultValue = false) const; void setRenderIntent(qint32 monitorRenderIntent) const; bool useOpenGL(bool defaultValue = false) const; void setUseOpenGL(bool useOpenGL) const; int openGLFilteringMode(bool defaultValue = false) const; void setOpenGLFilteringMode(int filteringMode); bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); bool showAdvancedOpenGLSettings(bool defaultValue = false) const; bool forceOpenGLFenceWorkaround(bool defaultValue = false) const; int numMipmapLevels(bool defaultValue = false) const; int openGLTextureSize(bool defaultValue = false) const; int textureOverlapBorder() const; quint32 getGridMainStyle(bool defaultValue = false) const; void setGridMainStyle(quint32 v) const; quint32 getGridSubdivisionStyle(bool defaultValue = false) const; void setGridSubdivisionStyle(quint32 v) const; QColor getGridMainColor(bool defaultValue = false) const; void setGridMainColor(const QColor & v) const; QColor getGridSubdivisionColor(bool defaultValue = false) const; void setGridSubdivisionColor(const QColor & v) const; QColor getPixelGridColor(bool defaultValue = false) const; void setPixelGridColor(const QColor & v) const; qreal getPixelGridDrawingThreshold(bool defaultValue = false) const; void setPixelGridDrawingThreshold(qreal v) const; bool pixelGridEnabled(bool defaultValue = false) const; void enablePixelGrid(bool v) const; quint32 guidesLineStyle(bool defaultValue = false) const; void setGuidesLineStyle(quint32 v) const; QColor guidesColor(bool defaultValue = false) const; void setGuidesColor(const QColor & v) const; void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const; void saveSnapConfig(const KisSnapConfig &config); qint32 checkSize(bool defaultValue = false) const; void setCheckSize(qint32 checkSize) const; bool scrollCheckers(bool defaultValue = false) const; void setScrollingCheckers(bool scollCheckers) const; QColor checkersColor1(bool defaultValue = false) const; void setCheckersColor1(const QColor & v) const; QColor checkersColor2(bool defaultValue = false) const; void setCheckersColor2(const QColor & v) const; QColor canvasBorderColor(bool defaultValue = false) const; void setCanvasBorderColor(const QColor &color) const; bool hideScrollbars(bool defaultValue = false) const; void setHideScrollbars(bool value) const; bool antialiasCurves(bool defaultValue = false) const; void setAntialiasCurves(bool v) const; bool antialiasSelectionOutline(bool defaultValue = false) const; void setAntialiasSelectionOutline(bool v) const; bool showRootLayer(bool defaultValue = false) const; void setShowRootLayer(bool showRootLayer) const; bool showGlobalSelection(bool defaultValue = false) const; void setShowGlobalSelection(bool showGlobalSelection) const; bool showOutlineWhilePainting(bool defaultValue = false) const; void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const; bool forceAlwaysFullSizedOutline(bool defaultValue = false) const; void setForceAlwaysFullSizedOutline(bool value) const; enum SessionOnStartup { SOS_BlankSession, SOS_PreviousSession, SOS_ShowSessionManager }; SessionOnStartup sessionOnStartup(bool defaultValue = false) const; void setSessionOnStartup(SessionOnStartup value); bool saveSessionOnQuit(bool defaultValue) const; void setSaveSessionOnQuit(bool value); qreal outlineSizeMinimum(bool defaultValue = false) const; void setOutlineSizeMinimum(qreal outlineSizeMinimum) const; qreal selectionViewSizeMinimum(bool defaultValue = false) const; void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const; int autoSaveInterval(bool defaultValue = false) const; void setAutoSaveInterval(int seconds) const; bool backupFile(bool defaultValue = false) const; void setBackupFile(bool backupFile) const; bool showFilterGallery(bool defaultValue = false) const; void setShowFilterGallery(bool showFilterGallery) const; bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const; void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const; // OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED QString canvasState(bool defaultValue = false) const; void setCanvasState(const QString& state) const; bool toolOptionsPopupDetached(bool defaultValue = false) const; void setToolOptionsPopupDetached(bool detached) const; bool paintopPopupDetached(bool defaultValue = false) const; void setPaintopPopupDetached(bool detached) const; QString pressureTabletCurve(bool defaultValue = false) const; void setPressureTabletCurve(const QString& curveString) const; bool useWin8PointerInput(bool defaultValue = false) const; void setUseWin8PointerInput(bool value); static bool useWin8PointerInputNoApp(QSettings *settings, bool defaultValue = false); static void setUseWin8PointerInputNoApp(QSettings *settings, bool value); qreal vastScrolling(bool defaultValue = false) const; void setVastScrolling(const qreal factor) const; int presetChooserViewMode(bool defaultValue = false) const; void setPresetChooserViewMode(const int mode) const; int presetIconSize(bool defaultValue = false) const; void setPresetIconSize(const int value) const; bool firstRun(bool defaultValue = false) const; void setFirstRun(const bool firstRun) const; bool clicklessSpacePan(bool defaultValue = false) const; void setClicklessSpacePan(const bool toggle) const; int horizontalSplitLines(bool defaultValue = false) const; void setHorizontalSplitLines(const int numberLines) const; int verticalSplitLines(bool defaultValue = false) const; void setVerticalSplitLines(const int numberLines) const; bool hideDockersFullscreen(bool defaultValue = false) const; void setHideDockersFullscreen(const bool value) const; bool showDockers(bool defaultValue = false) const; void setShowDockers(const bool value) const; bool showStatusBar(bool defaultValue = false) const; void setShowStatusBar(const bool value) const; bool hideMenuFullscreen(bool defaultValue = false) const; void setHideMenuFullscreen(const bool value) const; bool hideScrollbarsFullscreen(bool defaultValue = false) const; void setHideScrollbarsFullscreen(const bool value) const; bool hideStatusbarFullscreen(bool defaultValue = false) const; void setHideStatusbarFullscreen(const bool value) const; bool hideTitlebarFullscreen(bool defaultValue = false) const; void setHideTitlebarFullscreen(const bool value) const; bool hideToolbarFullscreen(bool defaultValue = false) const; void setHideToolbarFullscreen(const bool value) const; bool fullscreenMode(bool defaultValue = false) const; void setFullscreenMode(const bool value) const; QStringList favoriteCompositeOps(bool defaultValue = false) const; void setFavoriteCompositeOps(const QStringList& compositeOps) const; QString exportConfigurationXML(const QString &filterId, bool defaultValue = false) const; KisPropertiesConfigurationSP exportConfiguration(const QString &filterId, bool defaultValue = false) const; void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; QString importConfiguration(const QString &filterId, bool defaultValue = false) const; void setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; bool useOcio(bool defaultValue = false) const; void setUseOcio(bool useOCIO) const; int favoritePresets(bool defaultValue = false) const; void setFavoritePresets(const int value); bool levelOfDetailEnabled(bool defaultValue = false) const; void setLevelOfDetailEnabled(bool value); KisOcioConfiguration ocioConfiguration(bool defaultValue = false) const; void setOcioConfiguration(const KisOcioConfiguration &cfg); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) const; int ocioLutEdgeSize(bool defaultValue = false) const; void setOcioLutEdgeSize(int value); bool ocioLockColorVisualRepresentation(bool defaultValue = false) const; void setOcioLockColorVisualRepresentation(bool value); bool useSystemMonitorProfile(bool defaultValue = false) const; void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const; QString defaultPalette(bool defaultValue = false) const; void setDefaultPalette(const QString& name) const; QString toolbarSlider(int sliderNumber, bool defaultValue = false) const; void setToolbarSlider(int sliderNumber, const QString &slider); int layerThumbnailSize(bool defaultValue = false) const; void setLayerThumbnailSize(int size); bool sliderLabels(bool defaultValue = false) const; void setSliderLabels(bool enabled); QString currentInputProfile(bool defaultValue = false) const; void setCurrentInputProfile(const QString& name); bool presetStripVisible(bool defaultValue = false) const; void setPresetStripVisible(bool visible); bool scratchpadVisible(bool defaultValue = false) const; void setScratchpadVisible(bool visible); bool showSingleChannelAsColor(bool defaultValue = false) const; void setShowSingleChannelAsColor(bool asColor); bool hidePopups(bool defaultValue = false) const; void setHidePopups(bool hidepopups); int numDefaultLayers(bool defaultValue = false) const; void setNumDefaultLayers(int num); quint8 defaultBackgroundOpacity(bool defaultValue = false) const; void setDefaultBackgroundOpacity(quint8 value); QColor defaultBackgroundColor(bool defaultValue = false) const; void setDefaultBackgroundColor(QColor value); enum BackgroundStyle { RASTER_LAYER = 0, CANVAS_COLOR = 1, FILL_LAYER = 2 }; BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const; void setDefaultBackgroundStyle(BackgroundStyle value); int lineSmoothingType(bool defaultValue = false) const; void setLineSmoothingType(int value); qreal lineSmoothingDistance(bool defaultValue = false) const; void setLineSmoothingDistance(qreal value); qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const; void setLineSmoothingTailAggressiveness(qreal value); bool lineSmoothingSmoothPressure(bool defaultValue = false) const; void setLineSmoothingSmoothPressure(bool value); bool lineSmoothingScalableDistance(bool defaultValue = false) const; void setLineSmoothingScalableDistance(bool value); qreal lineSmoothingDelayDistance(bool defaultValue = false) const; void setLineSmoothingDelayDistance(qreal value); bool lineSmoothingUseDelayDistance(bool defaultValue = false) const; void setLineSmoothingUseDelayDistance(bool value); bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const; void setLineSmoothingFinishStabilizedCurve(bool value); bool lineSmoothingStabilizeSensors(bool defaultValue = false) const; void setLineSmoothingStabilizeSensors(bool value); int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); bool trackTabletEventLatency(bool defaultValue = false) const; void setTrackTabletEventLatency(bool value); bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); bool shouldEatDriverShortcuts(bool defaultValue = false) const; bool testingCompressBrushEvents(bool defaultValue = false) const; void setTestingCompressBrushEvents(bool value); const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const; void setCustomColorSelectorColorSpace(const KoColorSpace *cs); bool useDirtyPresets(bool defaultValue = false) const; void setUseDirtyPresets(bool value); bool useEraserBrushSize(bool defaultValue = false) const; void setUseEraserBrushSize(bool value); bool useEraserBrushOpacity(bool defaultValue = false) const; void setUseEraserBrushOpacity(bool value); QColor getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QColor & v) const; QString getMDIBackgroundImage(bool defaultValue = false) const; void setMDIBackgroundImage(const QString & fileName) const; int workaroundX11SmoothPressureSteps(bool defaultValue = false) const; bool showCanvasMessages(bool defaultValue = false) const; void setShowCanvasMessages(bool show); bool compressKra(bool defaultValue = false) const; void setCompressKra(bool compress); bool toolOptionsInDocker(bool defaultValue = false) const; void setToolOptionsInDocker(bool inDocker); bool kineticScrollingEnabled(bool defaultValue = false) const; void setKineticScrollingEnabled(bool enabled); int kineticScrollingGesture(bool defaultValue = false) const; void setKineticScrollingGesture(int kineticScroll); int kineticScrollingSensitivity(bool defaultValue = false) const; void setKineticScrollingSensitivity(int sensitivity); bool kineticScrollingHiddenScrollbars(bool defaultValue = false) const; void setKineticScrollingHideScrollbars(bool scrollbar); void setEnableOpenGLFramerateLogging(bool value) const; bool enableOpenGLFramerateLogging(bool defaultValue = false) const; void setEnableBrushSpeedLogging(bool value) const; bool enableBrushSpeedLogging(bool defaultValue = false) const; void setEnableAmdVectorizationWorkaround(bool value); bool enableAmdVectorizationWorkaround(bool defaultValue = false) const; + void setDisableAVXOptimizations(bool value); + bool disableAVXOptimizations(bool defaultValue = false) const; + bool animationDropFrames(bool defaultValue = false) const; void setAnimationDropFrames(bool value); int scrubbingUpdatesDelay(bool defaultValue = false) const; void setScrubbingUpdatesDelay(int value); int scrubbingAudioUpdatesDelay(bool defaultValue = false) const; void setScrubbingAudioUpdatesDelay(int value); int audioOffsetTolerance(bool defaultValue = false) const; void setAudioOffsetTolerance(int value); bool switchSelectionCtrlAlt(bool defaultValue = false) const; void setSwitchSelectionCtrlAlt(bool value); bool convertToImageColorspaceOnImport(bool defaultValue = false) const; void setConvertToImageColorspaceOnImport(bool value); int stabilizerSampleSize(bool defaultValue = false) const; void setStabilizerSampleSize(int value); bool stabilizerDelayedPaint(bool defaultValue = false) const; void setStabilizerDelayedPaint(bool value); bool showBrushHud(bool defaultValue = false) const; void setShowBrushHud(bool value); QString brushHudSetting(bool defaultValue = false) const; void setBrushHudSetting(const QString &value) const; bool calculateAnimationCacheInBackground(bool defaultValue = false) const; void setCalculateAnimationCacheInBackground(bool value); QColor defaultAssistantsColor(bool defaultValue = false) const; void setDefaultAssistantsColor(const QColor &color) const; bool autoSmoothBezierCurves(bool defaultValue = false) const; void setAutoSmoothBezierCurves(bool value); bool activateTransformToolAfterPaste(bool defaultValue = false) const; void setActivateTransformToolAfterPaste(bool value); enum RootSurfaceFormat { BT709_G22 = 0, BT709_G10, BT2020_PQ }; RootSurfaceFormat rootSurfaceFormat(bool defaultValue = false) const; void setRootSurfaceFormat(RootSurfaceFormat value); static RootSurfaceFormat rootSurfaceFormat(QSettings *displayrc, bool defaultValue = false); static void setRootSurfaceFormat(QSettings *displayrc, RootSurfaceFormat value); bool useZip64(bool defaultValue = false) const; void setUseZip64(bool value); template void writeEntry(const QString& name, const T& value) { m_cfg.writeEntry(name, value); } template void writeList(const QString& name, const QList& value) { m_cfg.writeEntry(name, value); } template T readEntry(const QString& name, const T& defaultValue=T()) { return m_cfg.readEntry(name, defaultValue); } template QList readList(const QString& name, const QList& defaultValue=QList()) { return m_cfg.readEntry(name, defaultValue); } /// get the profile the color management system has stored for the given screen static const KoColorProfile* getScreenProfile(int screen); private: KisConfig(const KisConfig&); KisConfig& operator=(const KisConfig&) const; private: mutable KConfigGroup m_cfg; bool m_readOnly; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/kis_import_catcher.cc b/libs/ui/kis_import_catcher.cc index 45d2bc2f3a..364acfe3a3 100644 --- a/libs/ui/kis_import_catcher.cc +++ b/libs/ui/kis_import_catcher.cc @@ -1,150 +1,149 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_import_catcher.h" #include #include #include #include #include "kis_node_manager.h" #include "kis_count_visitor.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_node_commands_adapter.h" #include "kis_group_layer.h" #include "kis_progress_widget.h" #include "kis_config.h" #include "KisPart.h" #include "kis_shape_layer.h" struct KisImportCatcher::Private { public: KisDocument* doc; KisViewManager* view; QUrl url; QString layerType; QString prettyLayerName() const; void importAsPaintLayer(KisPaintDeviceSP device); void importAsTransparencyMask(KisPaintDeviceSP device); }; QString KisImportCatcher::Private::prettyLayerName() const { QString name = url.fileName(); return !name.isEmpty() ? name : url.toDisplayString(); } void KisImportCatcher::Private::importAsPaintLayer(KisPaintDeviceSP device) { KisLayerSP newLayer = new KisPaintLayer(view->image(), prettyLayerName(), OPACITY_OPAQUE_U8, device); KisNodeSP parent = 0; KisLayerSP currentActiveLayer = view->activeLayer(); if (currentActiveLayer) { parent = currentActiveLayer->parent(); } if (parent.isNull()) { parent = view->image()->rootLayer(); } KisNodeCommandsAdapter adapter(view); adapter.addNode(newLayer, parent, currentActiveLayer); } KisImportCatcher::KisImportCatcher(const QUrl &url, KisViewManager *view, const QString &layerType) : m_d(new Private) { m_d->doc = KisPart::instance()->createDocument(); m_d->view = view; m_d->url = url; m_d->layerType = layerType; connect(m_d->doc, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); bool result = m_d->doc->openUrl(url, KisDocument::DontAddToRecent); if (!result) { deleteMyself(); } } void KisImportCatcher::slotLoadingFinished() { KisImageWSP importedImage = m_d->doc->image(); importedImage->waitForDone(); if (importedImage && importedImage->projection()->exactBounds().isValid()) { if (m_d->layerType == "KisPaintLayer") { KisPaintDeviceSP dev = importedImage->projection(); adaptClipToImageColorSpace(dev, m_d->view->image()); m_d->importAsPaintLayer(dev); } else if (m_d->layerType == "KisShapeLayer") { KisShapeLayerSP shapeLayer = dynamic_cast(m_d->view->nodeManager()->createNode(m_d->layerType, false, importedImage->projection()).data()); KisShapeLayerSP imported = dynamic_cast(importedImage->rootLayer()->firstChild().data()); const QTransform thisInvertedTransform = shapeLayer->absoluteTransformation(0).inverted(); Q_FOREACH (KoShape *shape, imported->shapes()) { KoShape *clonedShape = shape->cloneShape(); clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapeLayer->addShape(clonedShape); } } else { m_d->view->nodeManager()->createNode(m_d->layerType, false, importedImage->projection()); } } deleteMyself(); } void KisImportCatcher::deleteMyself() { m_d->doc->deleteLater(); deleteLater(); } KisImportCatcher::~KisImportCatcher() { delete m_d; } void KisImportCatcher::adaptClipToImageColorSpace(KisPaintDeviceSP dev, KisImageSP image) { KisConfig cfg(true); if (cfg.convertToImageColorspaceOnImport() && *dev->colorSpace() != *image->colorSpace()) { /// XXX: do we need intent here? - KUndo2Command* cmd = dev->convertTo(image->colorSpace()); - delete cmd; + dev->convertTo(image->colorSpace()); } } diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp index ba5e040f89..5a9c9b0896 100644 --- a/libs/ui/kis_png_converter.cpp +++ b/libs/ui/kis_png_converter.cpp @@ -1,1395 +1,1393 @@ /* * Copyright (c) 2005-2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_converter.h" // A big thank to Glenn Randers-Pehrson for his wonderful // documentation of libpng available at // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html #ifndef PNG_MAX_UINT // Removed in libpng 1.4 #define PNG_MAX_UINT PNG_UINT_31_MAX #endif #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" #include #include "kis_undo_stores.h" namespace { int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha) { QString id = cs->id(); if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") { return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY; } if (id == "RGBA" || id == "RGBA16") { return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; } return -1; } bool colorSpaceIdSupported(const QString &id) { return id == "RGBA" || id == "RGBA16" || id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16"; } QPair getColorSpaceForColorType(int color_type, int color_nb_bits) { QPair r; if (color_type == PNG_COLOR_TYPE_PALETTE) { r.first = RGBAColorModelID.id(); r.second = Integer8BitsColorDepthID.id(); } else { if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { r.first = GrayAColorModelID.id(); } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) { r.first = RGBAColorModelID.id(); } if (color_nb_bits == 16) { r.second = Integer16BitsColorDepthID.id(); } else if (color_nb_bits <= 8) { r.second = Integer8BitsColorDepthID.id(); } } return r; } void fillText(png_text* p_text, const char* key, QString& text) { p_text->compression = PNG_TEXT_COMPRESSION_zTXt; p_text->key = const_cast(key); char* textc = new char[text.length()+1]; strcpy(textc, text.toLatin1()); p_text->text = textc; p_text->text_length = text.length() + 1; } long formatStringList(char *string, const size_t length, const char *format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } long formatString(char *string, const size_t length, const char *format, ...) { long n; va_list operands; va_start(operands, format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data) { png_textp text; png_uint_32 allocated_length, description_length; const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl; text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); description_length = profile_type.length(); allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length); text[0].text = (png_charp) png_malloc(ping, allocated_length); QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1(); QByteArray keyData = key.toLatin1(); text[0].key = keyData.data(); uchar* sp = (uchar*)profile_data.data(); png_charp dp = text[0].text; *dp++ = '\n'; memcpy(dp, profile_type.toLatin1().constData(), profile_type.length()); dp += description_length; *dp++ = '\n'; formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length()); dp += 8; for (long i = 0; i < (long) profile_data.length(); i++) { if (i % 36 == 0) *dp++ = '\n'; *(dp++) = (char) hex[((*sp >> 4) & 0x0f)]; *(dp++) = (char) hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (png_size_t)(dp - text[0].text); text[0].compression = -1; if (text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text); } QByteArray png_read_raw_profile(png_textp text) { QByteArray profile; static const unsigned char unhex[103] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; png_charp sp = text[0].text + 1; /* look for newline */ while (*sp != '\n') sp++; /* look for length */ while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; png_uint_32 length = (png_uint_32) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; if (length == 0) { return profile; } profile.resize(length); /* copy profile, skipping white space and column 1 "=" signs */ unsigned char *dp = (unsigned char*)profile.data(); png_uint_32 nibbles = length * 2; for (png_uint_32 i = 0; i < nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { return QByteArray(); } sp++; } if (i % 2 == 0) *dp = (unsigned char)(16 * unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return profile; } void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize) { dbgFile << "Decoding " << type << " " << text[0].key; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type); Q_ASSERT(exifIO); QByteArray rawProfile = png_read_raw_profile(text); if (headerSize > 0) { rawProfile.remove(0, headerSize); } if (rawProfile.size() > 0) { QBuffer buffer; buffer.setData(rawProfile); exifIO->loadFrom(store, &buffer); } else { dbgFile << "Decoding failed"; } } } KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode) { // Q_ASSERT(doc); // Q_ASSERT(adapter); m_doc = doc; m_stop = false; m_max_row = 0; m_image = 0; m_batchMode = batchMode; } KisPNGConverter::~KisPNGConverter() { } class KisPNGReadStream { public: KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { } int nextValue() { if (m_posinc == 0) { m_posinc = 8; m_buf++; } m_posinc -= m_depth; return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1)); } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGWriteStream { public: KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { *m_buf = 0; } void setNextValue(int v) { if (m_posinc == 0) { m_posinc = 8; m_buf++; *m_buf = 0; } m_posinc -= m_depth; *m_buf = (v << m_posinc) | *m_buf; } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGReaderAbstract { public: KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {} virtual ~KisPNGReaderAbstract() {} virtual png_bytep readLine() = 0; protected: png_structp png_ptr; int width, height; }; class KisPNGReaderLineByLine : public KisPNGReaderAbstract { public: KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) { png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); row_pointer = new png_byte[rowbytes]; } ~KisPNGReaderLineByLine() override { delete[] row_pointer; } png_bytep readLine() override { png_read_row(png_ptr, row_pointer, 0); return row_pointer; } private: png_bytep row_pointer; }; class KisPNGReaderFullImage : public KisPNGReaderAbstract { public: KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) { row_pointers = new png_bytep[height]; png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); for (int i = 0; i < height; i++) { row_pointers[i] = new png_byte[rowbytes]; } png_read_image(png_ptr, row_pointers); } ~KisPNGReaderFullImage() override { for (int i = 0; i < height; i++) { delete[] row_pointers[i]; } delete[] row_pointers; } png_bytep readLine() override { return row_pointers[y++]; } private: png_bytepp row_pointers; int y; }; static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); while (length) { int nr = in->read((char*)data, length); if (nr <= 0) { png_error(png_ptr, "Read Error"); return; } length -= nr; } } static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr); uint nr = out->write((char*)data, length); if (nr != length) { png_error(png_ptr, "Write Error"); return; } } static void _flush_fn(png_structp png_ptr) { Q_UNUSED(png_ptr); } KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod) { dbgFile << "Start decoding PNG File"; png_byte signature[8]; iod->peek((char*)signature, 8); #if PNG_LIBPNG_VER < 10400 if (!png_check_sig(signature, 8)) { #else if (png_sig_cmp(signature, 0, 8) != 0) { #endif iod->close(); return (KisImageBuilder_RESULT_BAD_FETCH); } // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { iod->close(); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the special png_set_read_fn(png_ptr, iod, _read_fn); #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif // read all PNG info up to image data png_read_info(png_ptr, info_ptr); if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_expand(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_packing(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE && (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { png_set_expand(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Read information about the png png_uint_32 width, height; int color_nb_bits, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0); dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl; // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Determine the colorspace QPair csName = getColorSpaceForColorType(color_type, color_nb_bits); if (csName.first.isEmpty()) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA); // Read image profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; // Get the various optional chunks // https://www.w3.org/TR/PNG/#11cHRM #if defined(PNG_cHRM_SUPPORTED) double whitePointX, whitePointY; double redX, redY; double greenX, greenY; double blueX, blueY; png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY); dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY; #endif // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) double gamma; png_get_gAMA(png_ptr, info_ptr, &gamma); dbgFile << "gAMA" << gamma; #endif // https://www.w3.org/TR/PNG/#11sRGB #if defined(PNG_sRGB_SUPPORTED) int sRGBIntent; png_get_sRGB(png_ptr, info_ptr, &sRGBIntent); dbgFile << "sRGB" << sRGBIntent; #endif bool fromBlender = false; png_text* text_ptr; int num_comments; png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); if (key == "file") { QString relatedFile = text_ptr[i].text; if (relatedFile.contains(".blend", Qt::CaseInsensitive)){ fromBlender=true; } } } bool loadedImageIsHDR = false; const KoColorProfile* profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { QByteArray profile_rawdata; // XXX: Hardcoded for icc type -- is that correct for us? profile_rawdata.resize(proflen); memcpy(profile_rawdata.data(), profile_data, proflen); profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata); Q_CHECK_PTR(profile); if (profile) { // dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } loadedImageIsHDR = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0; } else { dbgFile << "no embedded profile, will use the default profile"; if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) { KisConfig cfg(true); quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); KisCursorOverrideHijacker hijacker; Q_UNUSED(hijacker); dlg.exec(); if (!dlg.profile().isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile()); } } } dbgFile << "no embedded profile, will use the default profile"; } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second; profile = 0; } // Retrieve a pointer to the colorspace KoColorConversionTransformation* transform = 0; const KoColorSpace* cs = 0; if (loadedImageIsHDR && csName.first == RGBAColorModelID.id() && csName.second == Integer16BitsColorDepthID.id()) { const KoColorSpace *p2020PQCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile()); cs = p2020PQCS; } else if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile: " << profile->name() << "\n"; cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile); } else { if (csName.first == RGBAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc"); } else if (csName.first == GrayAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-elle-V2-srgbtrc.icc"); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0); } //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation // Create the cmsTransform if needed if (profile) { transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } if (cs == 0) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Creating the KisImageSP if (m_image == 0) { KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore(); m_image = new KisImage(store, width, height, cs, "built image"); } // Read resolution int unit_type; png_uint_32 x_resolution, y_resolution; png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type); if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) { m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points } double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1); KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX); // Read comments/texts... png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); if (m_doc) { KoDocumentInfo * info = m_doc->documentInfo(); dbgFile << "There are " << num_comments << " comments in the text"; for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); dbgFile << "key: " << text_ptr[i].key << ", containing: " << text_ptr[i].text << ": " << (key == "raw profile type exif " ? "isExif" : "something else"); if (key == "title") { info->setAboutInfo("title", text_ptr[i].text); } else if (key == "description") { info->setAboutInfo("comment", text_ptr[i].text); } else if (key == "author") { info->setAuthorInfo("creator", text_ptr[i].text); } else if (key.contains("raw profile type exif")) { decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6); } else if (key.contains("raw profile type iptc")) { decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14); } else if (key.contains("raw profile type xmp")) { decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0); } else if (key == "version") { m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text))); } else if (key == "preset") { m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text))); } } } // Read image data QScopedPointer reader; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height)); } else { reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height)); } } catch (const std::bad_alloc& e) { // new png_byte[] may raise such an exception if the image // is invalid / to large. dbgFile << "bad alloc: " << e.what(); // Free only the already allocated png_byte instances. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (KisImageBuilder_RESULT_FAILURE); } // Read the palette if the file is indexed png_colorp palette ; int num_palette; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } // Read the transparency palette quint8 palette_alpha[256]; memset(palette_alpha, 255, 256); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { if (color_type == PNG_COLOR_TYPE_PALETTE) { png_bytep alpha_ptr; int num_alpha; png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0); for (int i = 0; i < num_alpha; ++i) { palette_alpha[i] = alpha_ptr[i]; } } } for (png_uint_32 y = 0; y < height; y++) { KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width); png_bytep row_pointer = reader->readLine(); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[0] = *(src++); if (hasalpha) { d[1] = *(src++); } else { d[1] = quint16_MAX; } if (transform) transform->transformInPlace(reinterpret_cast(d), reinterpret_cast(d), 1); } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[0] = (quint8)(stream.nextValue() * coeff); if (hasalpha) { d[1] = (quint8)(stream.nextValue() * coeff); } else { d[1] = UCHAR_MAX; } if (transform) transform->transformInPlace(d, d, 1); } while (it->nextPixel()); } // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits" break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (hasalpha) d[3] = *(src++); else d[3] = quint16_MAX; if (transform) transform->transformInPlace(reinterpret_cast(d), reinterpret_cast(d), 1); } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[2] = (quint8)(stream.nextValue() * coeff); d[1] = (quint8)(stream.nextValue() * coeff); d[0] = (quint8)(stream.nextValue() * coeff); if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff); else d[3] = UCHAR_MAX; if (transform) transform->transformInPlace(d, d, 1); } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); quint8 index = stream.nextValue(); quint8 alpha = palette_alpha[ index ]; if (alpha == 0) { memset(d, 0, 4); } else { png_color c = palette[ index ]; d[2] = c.red; d[1] = c.green; d[0] = c.blue; d[3] = alpha; } } while (it->nextPixel()); } break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_image->addNode(layer.data(), m_image->rootLayer().data()); png_read_end(png_ptr, end_info); iod->close(); // Freeing memory png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename) { m_path = filename; QFile fp(filename); if (fp.exists()) { if (!fp.open(QIODevice::ReadOnly)) { dbgFile << "Failed to open PNG File"; return (KisImageBuilder_RESULT_FAILURE); } return buildImage(&fp); } return (KisImageBuilder_RESULT_NOT_EXIST); } KisImageSP KisPNGConverter::image() { return m_image; } bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData) { if (store->open(filename)) { KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { dbgFile << "Could not open for writing:" << filename; return false; } KisPNGConverter pngconv(0); vKisAnnotationSP_it annotIt = 0; KisMetaData::Store* metaDataStore = 0; if (metaData) { metaDataStore = new KisMetaData::Store(*metaData); } KisPNGOptions options; options.compression = 0; options.interlace = false; options.tryToSaveAsIndexed = false; options.alpha = true; options.saveSRGBProfile = false; if (dev->colorSpace()->id() != "RGBA") { dev = new KisPaintDevice(*dev.data()); - KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); - delete cmd; + dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); if (success != KisImageBuilder_RESULT_OK) { dbgFile << "Saving PNG failed:" << filename; delete metaDataStore; return false; } delete metaDataStore; io.close(); if (!store->close()) { return false; } } else { dbgFile << "Opening of data file failed :" << filename; return false; } return true; } KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { dbgFile << "Start writing PNG File " << filename; // Open a QIODevice for writing QFile fp (filename); if (!fp.open(QIODevice::WriteOnly)) { dbgFile << "Failed to open PNG File for writing"; return (KisImageBuilder_RESULT_FAILURE); } KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); return result; } KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { if (!device) return KisImageBuilder_RESULT_INVALID_ARG; if (!options.alpha) { KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); KoColor c(options.transparencyFillColor, device->colorSpace()); tmp->fill(imageRect, c); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID || options.saveAsHDR) { const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace( device->colorSpace()->colorModelId().id(), Integer16BitsColorDepthID.id(), device->colorSpace()->profile()); if (options.saveAsHDR) { dstCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile()); } KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); tmp->makeCloneFromRough(device, imageRect); - delete tmp->convertTo(dstCS); + tmp->convertTo(dstCS); device = tmp; } KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.forceSRGB) { options.forceSRGB = false; } KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.tryToSaveAsIndexed) { options.tryToSaveAsIndexed = false; } QStringList colormodels = QStringList() << RGBAColorModelID.id() << GrayAColorModelID.id(); if (options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); device = new KisPaintDevice(*device); - KUndo2Command *cmd = device->convertTo(cs); - delete cmd; + device->convertTo(cs); } // Initialize structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { return (KisImageBuilder_RESULT_FAILURE); } #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED png_set_check_for_invalid_index(png_ptr, 0); #endif png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)0); return (KisImageBuilder_RESULT_FAILURE); } // If an error occurs during writing, libpng will jump here if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the writing // png_init_io(png_ptr, fp); // Setup the progress function // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);" // setProgressTotalSteps(100/*height*/); /* set the zlib compression level */ png_set_compression_level(png_ptr, options.compression); png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn); /* set other zlib parameters */ png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); png_set_compression_buffer_size(png_ptr, 8192); int color_nb_bits = 8 * device->pixelSize() / device->channelCount(); int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha); Q_ASSERT(color_type > -1); // Try to compute a table of color if the colorspace is RGB8f QScopedArrayPointer palette; int num_palette = 0; if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8 palette.reset(new png_color[255]); KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; while (it.nextPixel()) { const quint8* c = it.oldRawData(); bool findit = false; for (int i = 0; i < num_palette; i++) { if (palette[i].red == c[2] && palette[i].green == c[1] && palette[i].blue == c[0]) { findit = true; break; } } if (!findit) { if (num_palette == 255) { toomuchcolor = true; break; } palette[num_palette].red = c[2]; palette[num_palette].green = c[1]; palette[num_palette].blue = c[0]; num_palette++; } } if (!toomuchcolor) { dbgFile << "Found a palette of " << num_palette << " colors"; color_type = PNG_COLOR_TYPE_PALETTE; if (num_palette <= 2) { color_nb_bits = 1; } else if (num_palette <= 4) { color_nb_bits = 2; } else if (num_palette <= 16) { color_nb_bits = 4; } else { color_nb_bits = 8; } } else { palette.reset(); } } int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, KisImageBuilder_RESULT_FAILURE); png_set_IHDR(png_ptr, info_ptr, imageRect.width(), imageRect.height(), color_nb_bits, color_type, interlacetype, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); /* * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's * color management is bugged, so once you give it any incentive to start color managing an sRGB image it * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now. */ /*if (!options.saveSRGBProfile && sRGB) { png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); }*/ /** TODO: Firefox still opens the image incorrectly if there is gAMA+cHRM tags * present. According to the standard it should use iCCP tag with higher priority, * but it doesn't: * * "When the iCCP chunk is present, PNG decoders that recognize it and are capable * of colour management [ICC] shall ignore the gAMA and cHRM chunks and use * the iCCP chunk instead and interpret it according to [ICC-1] and [ICC-1A]" */ #if 0 if (options.saveAsHDR) { // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) // the values are set in accurdance of HDR-PNG standard: // https://www.w3.org/TR/png-hdr-pq/ png_set_gAMA_fixed(png_ptr, info_ptr, 15000); dbgFile << "gAMA" << "(Rec 2100)"; #endif #if defined PNG_cHRM_SUPPORTED png_set_cHRM_fixed(png_ptr, info_ptr, 31270, 32900, // white point 70800, 29200, // red 17000, 79700, // green 13100, 4600 // blue ); dbgFile << "cHRM" << "(Rec 2100)"; #endif } #endif // we should ensure we don't access non-existing palette object KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, KisImageBuilder_RESULT_FAILURE); // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette); } // Save annotation vKisAnnotationSP_it it = annotationsStart; while (it != annotationsEnd) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size(); if ((*it) -> type().startsWith(QString("krita_attribute:"))) { // // Attribute // XXX: it should be possible to save krita_attributes in the \"CHUNKs\"" dbgFile << "cannot save this annotation : " << (*it) -> type(); } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) { dbgFile << "Saving preset information " << (*it)->description(); png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text)); QByteArray keyData = (*it)->description().toLatin1(); text[0].key = keyData.data(); text[0].text = (char*)(*it)->annotation().data(); text[0].text_length = (*it)->annotation().size(); text[0].compression = -1; png_set_text(png_ptr, info_ptr, text, 1); png_free(png_ptr, text); } it++; } // Save the color profile const KoColorProfile* colorProfile = device->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); if (!sRGB || options.saveSRGBProfile) { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 const char *typeString = !options.saveAsHDR ? "icc" : "ITUR_2100_PQ_FULL"; png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size()); #else // older version of libpng has a problem with constness on the parameters char typeStringICC[] = "icc"; char typeStringHDR[] = "ITUR_2100_PQ_FULL"; char *typeString = !options.saveAsHDR ? typeStringICC : typeStringHDR; png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size()); #endif } // save comments from the document information // warning: according to the official png spec, the keys need to be capitalized! if (m_doc) { png_text texts[4]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Title", title); nbtexts++; } QString abstract = info->aboutInfo("subject"); if (abstract.isEmpty()) { abstract = info->aboutInfo("abstract"); } if (!abstract.isEmpty() && options.storeMetaData) { QString keywords = info->aboutInfo("keyword"); if (!keywords.isEmpty()) { abstract = abstract + " keywords: " + keywords; } fillText(texts + nbtexts, "Description", abstract); nbtexts++; } QString license = info->aboutInfo("license"); if (!license.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Copyright", license); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty() && options.storeAuthor) { if (!info->authorContactInfo().isEmpty()) { QString contact = info->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } fillText(texts + nbtexts, "Author", author); nbtexts++; } png_set_text(png_ptr, info_ptr, texts, nbtexts); } // Save metadata following imagemagick way // Save exif if (metaData && !metaData->empty()) { if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); writeRawProfile(png_ptr, info_ptr, "exif", buffer.data()); } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data()); } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader); dbgFile << "XMP information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data()); } } #if 0 // Unimplemented? // Save resolution int unit_type; png_uint_32 x_resolution, y_resolution; #endif png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points // Save the information to the file png_write_info(png_ptr, info_ptr); png_write_flush(png_ptr); // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Write the PNG // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0); struct RowPointersStruct { RowPointersStruct(const QSize &size, int pixelSize) : numRows(size.height()) { rows = new png_byte*[numRows]; for (int i = 0; i < numRows; i++) { rows[i] = new png_byte[size.width() * pixelSize]; } } ~RowPointersStruct() { for (int i = 0; i < numRows; i++) { delete[] rows[i]; } delete[] rows; } const int numRows = 0; png_byte** rows = 0; }; // Fill the data structure RowPointersStruct rowPointers(imageRect.size(), device->pixelSize()); int row = 0; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { quint8 *dst = rowPointers.rows[row]; KisPNGWriteStream writestream(dst, color_nb_bits); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < num_palette; i++) { if (palette[i].red == d[2] && palette[i].green == d[1] && palette[i].blue == d[0]) { break; } } writestream.setNextValue(i); } while (it->nextPixel()); } break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } png_write_image(png_ptr, rowPointers.rows); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); return KisImageBuilder_RESULT_OK; } void KisPNGConverter::cancel() { m_stop = true; } void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass) { if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return; // setProgress(row_number); } bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs) { return colorSpaceIdSupported(cs->id()); } diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index 2207202d02..ff0c835d91 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,980 +1,994 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include "kis_signal_compressor.h" #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include "kis_signals_blocker.h" #include "kis_canvas_controller.h" #include "kis_acyclic_signal_connector.h" #include class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; - switch (event->type()) { - case QEvent::TabletPress: - mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), - Qt::LeftButton, Qt::LeftButton, event->modifiers()); - m_dragging = true; - mousePressEvent(mouseEvent); - break; - case QEvent::TabletMove: - mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), - (m_dragging) ? Qt::LeftButton : Qt::NoButton, - (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); - mouseMoveEvent(mouseEvent); - break; - case QEvent::TabletRelease: - mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), - Qt::LeftButton, - Qt::LeftButton, - event->modifiers()); - m_dragging = false; - mouseReleaseEvent(mouseEvent); - break; - default: break; + + // this will tell the pop-up palette widget to close + if(event->button() == Qt::RightButton) { + emit requestCloseContainer(); } + + // ignore any tablet events that are done with the right click + // Tablet move events don't return a "button", so catch that too + if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove) + { + switch (event->type()) { + case QEvent::TabletPress: + mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), + Qt::LeftButton, Qt::LeftButton, event->modifiers()); + m_dragging = true; + mousePressEvent(mouseEvent); + break; + case QEvent::TabletMove: + mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), + (m_dragging) ? Qt::LeftButton : Qt::NoButton, + (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); + mouseMoveEvent(mouseEvent); + break; + case QEvent::TabletRelease: + mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), + Qt::LeftButton, + Qt::LeftButton, + event->modifiers()); + m_dragging = false; + mouseReleaseEvent(mouseEvent); + break; + default: break; + } + } + delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_coordinatesConverter(coordinatesConverter) , m_viewManager(viewManager) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_acyclicConnector(new KisAcyclicSignalConnector(this)) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig(true).readEntry("popuppalette/usevisualcolorselector", false)) { m_triangleColorSelector = new KisVisualColorSelector(this); } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); + connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide())); + connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), this, SLOT(slotExternalFgColorChanged(KoColor))); m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setFixedHeight(int(m_popupPaletteSize)); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg(true); m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setFixedSize(35, 35); mirrorMode->setToolTip(i18n("Mirror Canvas")); mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas")); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setToolTip(i18n("Canvas Only")); canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only")); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed())); connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased())); slotUpdateIcons(); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); opacityChange = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacityChange); // Prevent tablet events from being captured by the canvas setAttribute(Qt::WA_NoMousePropagation, true); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::slotZoomSliderPressed() { m_isZoomingCanvas = true; } void KisPopupPalette::slotZoomSliderReleased() { m_isZoomingCanvas = false; } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::slotUpdateIcons() { zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon()); m_brushHud->updateIcons(); m_settingsButton->setIcon(KisIconUtils::loadIcon("tag")); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg(false); cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { m_hadMousePressSinceOpening = false; m_timeSinceOpening.start(); // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ KisSignalsBlocker b(zoomCanvasSlider); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); // painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); // painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); // painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); // painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } // painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } // painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) { opacityChange->setOpacity(0.4); } else { opacityChange->setOpacity(1.0); } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); #ifdef Q_OS_WIN const int tableMouseEventsFlowDelay = 500; #else const int tableMouseEventsFlowDelay = 100; #endif /** * Tablet support code generates a spurious right-click right after opening * the window, so we should ignore it. Next right-click will be used for * closing the popup palette */ if (!m_hadMousePressSinceOpening && m_timeSinceOpening.elapsed() > tableMouseEventsFlowDelay) { m_hadMousePressSinceOpening = true; } if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { // KisTagModel *model = new KisTagModel(ResourceType::PaintOpPresets); // QSet tags; // for (int i = 0; i < model.rowCount(); ++i) { // QModelIndex idx = model.index(i, 0); // tags << model.data(idx, Qt::DisplayRole).toString(); // } // std::sort(tags.begin(), tags.end()); // if (!tags.isEmpty()) { // QMenu menu; // Q_FOREACH (const QString& tag, tags) { // menu.addAction(tag); // } // QAction *action = menu.exec(QCursor::pos()); // if (action) { // m_resourceManager->setCurrentTag(action->text()); // } // } else { // QWhatsThis::showText(QCursor::pos(), // i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); // } // delete model; } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction *action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent *event) { event->ignore(); } void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); // see a comment in KisPopupPalette::mousePressEvent if (m_hadMousePressSinceOpening && event->buttons() == Qt::NoButton && event->button() == Qt::RightButton) { showPopupPalette(false); return; } m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal radians = qDegreesToRadians((360.0/10)/2); qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2; radians = qDegreesToRadians(angleSlice/2); qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)); //If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking. qreal length = m_colorHistoryOuterRadius + presetRadius; // can we can fit in a second row? We don't want the preset icons to get too tiny. if (maxRadius > presetRadius) { //redo all calculations assuming a second row. if (numSlots() % 2) { angleSlice = 360.0/(numSlots()+1); startingAngle = -(index * angleSlice) + 90; } if (numSlots() != m_cachedNumSlots){ qreal tempRadius = presetRadius; qreal distance = 0; do{ tempRadius+=0.1; // Calculate the XY of two adjectant circles using this tempRadius. qreal length1 = m_colorHistoryOuterRadius + tempRadius; qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius); qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius; qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius; qreal startingAngle2 = -(index+1 * angleSlice) + 90; qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius; qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius; // Use Pythagorean Theorem to calculate the distance between these two values. qreal m1 = pathX2-pathX1; qreal m2 = pathY2-pathY1; distance = sqrt((m1*m1)+(m2*m2)); } //As long at there's more distance than the radius of the two presets, continue increasing the radius. while((tempRadius+1)*2 < distance); m_cachedRadius = tempRadius; } m_cachedNumSlots = numSlots(); presetRadius = m_cachedRadius; length = m_colorHistoryOuterRadius + presetRadius; if (index % 2) { length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius); } } QPainterPath path; qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config(true); return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp index 9f78f958db..40df8bfe68 100644 --- a/libs/ui/widgets/kis_slider_spin_box.cpp +++ b/libs/ui/widgets/kis_slider_spin_box.cpp @@ -1,1053 +1,1051 @@ /* This file is part of the KDE project * Copyright (c) 2010 Justin Noel * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2015 Moritz Molch * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_slider_spin_box.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_cursor.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include "kis_num_parser.h" class KisAbstractSliderSpinBoxPrivate { public: enum Style { STYLE_NOQUIRK, STYLE_PLASTIQUE, STYLE_BREEZE, STYLE_FUSION, }; QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; qreal slowFactor; qreal shiftPercent; bool shiftMode; QString prefix; QString suffix; qreal exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; Style style; bool blockUpdateSignalOnDrag; bool isDragging; bool parseInt; }; KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d) : QWidget(parent) , d_ptr(_d) { Q_D(KisAbstractSliderSpinBox); QEvent e(QEvent::StyleChange); changeEvent(&e); d->upButtonDown = false; d->downButtonDown = false; d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->setContentsMargins(0,0,0,0); d->edit->installEventFilter(this); //Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); - + d->edit->setContextMenuPolicy(Qt::PreventContextMenu); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->value = 0; d->minimum = 0; d->maximum = 100; d->factor = 1.0; d->singleStep = 1; d->fastSliderStep = 5; d->slowFactor = 0.1; d->shiftMode = false; d->blockUpdateSignalOnDrag = false; d->isDragging = false; d->parseInt = false; setExponentRatio(1.0); setCursor(KisCursor::splitHCursor()); //Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); //dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox() { Q_D(KisAbstractSliderSpinBox); delete d; } void KisAbstractSliderSpinBox::showEdit() { Q_D(KisAbstractSliderSpinBox); if (d->edit->isVisible()) return; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) { d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0)); } else { d->edit->setGeometry(progressRect(spinBoxOptions())); } d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); } void KisAbstractSliderSpinBox::hideEdit() { Q_D(KisAbstractSliderSpinBox); d->edit->hide(); update(); } void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(KisAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: paintFusion(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: paintPlastique(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: paintBreeze(painter); break; default: paint(painter); break; } painter.end(); } void KisAbstractSliderSpinBox::paint(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); //Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.rect.adjust(0, 2, 0, -2); //Draw "SpinBox".Clip off the area of the lineEdit to avoid double //borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(progressRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); QStyleOptionProgressBar progressOpts = progressBarOptions(); progressOpts.rect.adjust(0, 2, 0, -2); style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); //Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void KisAbstractSliderSpinBox::paintFusion(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.frame = true; spinOpts.rect.adjust(0, -1, 0, 1); //spinOpts.palette().setBrush(QPalette::Base, palette().highlight()); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(1,2,-4,-2); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setClipRect(leftRect.adjusted(0, -1, 1, 1)); painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); spinOpts.palette.setBrush(QPalette::Base, palette().highlight()); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); } painter.setClipping(false); } painter.restore(); } void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(2,0,-2,0); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); painter.drawRect(leftRect.adjusted(0,0,0,-1)); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect.adjusted(0,0,1,0)); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } } painter.restore(); } void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); QString valueText = progressOpts.text; progressOpts.text = ""; progressOpts.rect.adjust(0, 1, 0, -1); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this); style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this); painter.save(); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) { leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height()); } else if (progressIndicatorPos > progressOpts.rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = progressOpts.rect; rightRect = rightRect.subtracted(leftRect); painter.setClipRegion(rightRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } if (!leftRect.isNull()) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect); style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } } painter.restore(); } void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Depress buttons or highlight slider //Also used to emulate mouse grab... if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } - update(); } void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); d->isDragging = false; //Step up/down for buttons //Emualting mouse grab too if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (progressRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { //Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } else { // Confirm the last known value, since we might be ignoring move events setInternalValue(d->value); } - d->upButtonDown = false; d->downButtonDown = false; update(); } void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; } } else { d->shiftMode = false; } //Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { d->isDragging = true; setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag); update(); } } void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(KisAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: //Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(KisAbstractSliderSpinBox); if ( e->delta() > 0) { setInternalValue(d->value + d->singleStep); } else { setInternalValue(d->value - d->singleStep); } update(); e->accept(); } bool KisAbstractSliderSpinBox::event(QEvent *event) { if (event->type() == QEvent::ShortcutOverride){ QKeyEvent* key = static_cast(event); if (key->modifiers() == Qt::NoModifier){ switch(key->key()){ case Qt::Key_Up: case Qt::Key_Right: case Qt::Key_Down: case Qt::Key_Left: event->accept(); return true; default: break; } } } return QWidget::event(event); } void KisAbstractSliderSpinBox::commitEnteredValue() { Q_D(KisAbstractSliderSpinBox); //QLocale locale; bool ok = false; //qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor; qreal value; if (d->parseInt) { value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor; } else { value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor; } if (ok) { setInternalValue(value); } } bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(KisAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: { commitEnteredValue(); hideEdit(); return true; } case Qt::Key_Escape: d->edit->setText(valueString()); hideEdit(); return true; default: break; } } return false; } QSize KisAbstractSliderSpinBox::sizeHint() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFont ft(font()); if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) { // Some styles use bold font in progressbars // unfortunately there is no reliable way to check for that ft.setBold(true); } QFontMetrics fm(ft); QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size()); hint += QSize(0, 2); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: hint += QSize(8, 8); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: hint += QSize(8, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: hint += QSize(2, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK: // almost all "modern" styles have a margin around controls hint += QSize(6, 6); break; default: break; } //Getting the size of the buttons is a pain as the calcs require a rect //that is "big enough". We run the calc twice to get the "smallest" buttons //This code was inspired by QAbstractSpinBox QSize extra(1000, 0); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect.setSize(hint); return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint) .expandedTo(QApplication::globalStrut()); } QSize KisAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QSize KisAbstractSliderSpinBox::minimumSize() const { return QWidget::minimumSize().expandedTo(minimumSizeHint()); } QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; //Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } //Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; qreal minDbl = d->minimum; qreal dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = d->prefix + valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); //Change opts rect to be only the ComboBox's text area progressOpts.rect = progressRect(spinOpts); return progressOpts; } QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const { const Q_D(KisAbstractSliderSpinBox); QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: ret.adjust(-2, 0, 1, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: ret.adjust(1, 0, 0, 0); break; default: break; } return ret; } QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QRect correctedProgRect; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) { correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0); } else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) { correctedProgRect = progressRect(spinOpts); } else { //Adjust for magic number in style code (margins) correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2); } //Compute the distance of the progress bar, in pixel qreal leftDbl = correctedProgRect.left(); qreal xDbl = x - leftDbl; //Compute the ration of the progress bar used, linearly (ignoring the exponent) qreal rightDbl = correctedProgRect.right(); qreal minDbl = d->minimum; qreal maxDbl = d->maximum; qreal dValues = (maxDbl - minDbl); qreal percent = (xDbl / (rightDbl - leftDbl)); //If SHIFT is pressed, movement should be slowed. if( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor; } //Final value qreal realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl); //If key CTRL is pressed, round to the closest step. if( modifiers & Qt::ControlModifier ) { qreal fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep*=d->slowFactor; } realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep; } //Return the value return int(realvalue); } void KisAbstractSliderSpinBox::setPrefix(const QString& prefix) { Q_D(KisAbstractSliderSpinBox); d->prefix = prefix; } void KisAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(KisAbstractSliderSpinBox); d->suffix = suffix; } void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl) { Q_D(KisAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->blockUpdateSignalOnDrag = blockUpdateSignal; } void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void KisAbstractSliderSpinBox::editLostFocus() { Q_D(KisAbstractSliderSpinBox); if (!d->edit->hasFocus()) { commitEnteredValue(); hideEdit(); } } void KisAbstractSliderSpinBox::setInternalValue(int value) { setInternalValue(value, false); } bool KisAbstractSliderSpinBox::isDragging() const { Q_D(const KisAbstractSliderSpinBox); return d->isDragging; } void KisAbstractSliderSpinBox::setPrivateValue(int value) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, value, d->maximum); } class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate) { Q_D(KisSliderSpinBox); d->parseInt = true; setRange(0,99); } KisSliderSpinBox::~KisSliderSpinBox() { } void KisSliderSpinBox::setRange(int minimum, int maximum) { Q_D(KisSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int KisSliderSpinBox::minimum() const { const Q_D(KisSliderSpinBox); return d->minimum; } void KisSliderSpinBox::setMinimum(int minimum) { Q_D(KisSliderSpinBox); setRange(minimum, d->maximum); } int KisSliderSpinBox::maximum() const { const Q_D(KisSliderSpinBox); return d->maximum; } void KisSliderSpinBox::setMaximum(int maximum) { Q_D(KisSliderSpinBox); setRange(d->minimum, maximum); } int KisSliderSpinBox::fastSliderStep() const { const Q_D(KisSliderSpinBox); return d->fastSliderStep; } void KisSliderSpinBox::setFastSliderStep(int step) { Q_D(KisSliderSpinBox); d->fastSliderStep = step; } int KisSliderSpinBox::value() { Q_D(KisSliderSpinBox); return d->value; } void KisSliderSpinBox::setValue(int value) { setInternalValue(value, false); update(); } QString KisSliderSpinBox::valueString() const { const Q_D(KisSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value, 'f', d->validator->decimals()); } void KisSliderSpinBox::setSingleStep(int value) { Q_D(KisSliderSpinBox); d->singleStep = value; } void KisSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate) { Q_D(KisDoubleSliderSpinBox); d->parseInt = false; } KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox() { } void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals) { Q_D(KisDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; //This code auto-compute a new step when pressing control. //A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } qreal KisDoubleSliderSpinBox::minimum() const { const Q_D(KisAbstractSliderSpinBox); return d->minimum / d->factor; } void KisDoubleSliderSpinBox::setMinimum(qreal minimum) { Q_D(KisAbstractSliderSpinBox); setRange(minimum, d->maximum); } qreal KisDoubleSliderSpinBox::maximum() const { const Q_D(KisAbstractSliderSpinBox); return d->maximum / d->factor; } void KisDoubleSliderSpinBox::setMaximum(qreal maximum) { Q_D(KisAbstractSliderSpinBox); setRange(d->minimum, maximum); } qreal KisDoubleSliderSpinBox::fastSliderStep() const { const Q_D(KisAbstractSliderSpinBox); return d->fastSliderStep; } void KisDoubleSliderSpinBox::setFastSliderStep(qreal step) { Q_D(KisAbstractSliderSpinBox); d->fastSliderStep = step; } qreal KisDoubleSliderSpinBox::value() { Q_D(KisAbstractSliderSpinBox); return (qreal)d->value / d->factor; } void KisDoubleSliderSpinBox::setValue(qreal value) { Q_D(KisAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor), false); update(); } void KisDoubleSliderSpinBox::setSingleStep(qreal value) { Q_D(KisAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString KisDoubleSliderSpinBox::valueString() const { const Q_D(KisAbstractSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals()); } void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } void KisAbstractSliderSpinBox::changeEvent(QEvent *e) { Q_D(KisAbstractSliderSpinBox); QWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: if (style()->objectName() == "fusion") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION; } else if (style()->objectName() == "plastique") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE; } else if (style()->objectName() == "breeze") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE; } else { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK; } break; default: break; } } diff --git a/libs/widgets/KoTriangleColorSelector.h b/libs/widgets/KoTriangleColorSelector.h index 113c4abea7..89c00f1bc6 100644 --- a/libs/widgets/KoTriangleColorSelector.h +++ b/libs/widgets/KoTriangleColorSelector.h @@ -1,70 +1,71 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KO_TRIANGLE_COLOR_SELECTOR_H_ #define _KO_TRIANGLE_COLOR_SELECTOR_H_ #include #include "kritawidgets_export.h" #include class KoColor; class KoColorDisplayRendererInterface; class KRITAWIDGETS_EXPORT KoTriangleColorSelector : public KisColorSelectorInterface { Q_OBJECT public: explicit KoTriangleColorSelector(QWidget *parent); explicit KoTriangleColorSelector(const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent); ~KoTriangleColorSelector() override; protected: // events void paintEvent( QPaintEvent * event ) override; void resizeEvent( QResizeEvent * event ) override; void mouseReleaseEvent( QMouseEvent * event ) override; void mousePressEvent( QMouseEvent * event ) override; void mouseMoveEvent( QMouseEvent * event ) override; public: int hue() const; int value() const; int saturation() const; KoColor getCurrentColor() const override; public Q_SLOTS: void setHue(int h); void setValue(int v); void setSaturation(int s); void setHSV(int h, int s, int v); void slotSetColor(const KoColor& ) override; Q_SIGNALS: void colorChanged(const QColor& ); + void requestCloseContainer(); private Q_SLOTS: void configurationChanged(); private: void tellColorChanged(); void generateTriangle(); void generateWheel(); void updateTriangleCircleParameters(); void selectColorAt(int x, int y, bool checkInWheel = true); private: struct Private; Private* const d; }; #endif diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh index 2737ff915a..ea65ce7341 100755 --- a/packaging/macos/osxbuild.sh +++ b/packaging/macos/osxbuild.sh @@ -1,418 +1,427 @@ #!/usr/bin/env sh # Stop at any error # For debug purposes # set -e # set -x # osxbuild.sh automates building and installing of krita and krita dependencies # for OSX, the script only needs you to set BUILDROOT environment to work # properly. # # Run with no args for a short help about each command. # builddeps: Attempts to build krita dependencies in the necessary order, # intermediate steps for creating symlinks and fixing rpath of some # packages midway is also managed. Order goes from top to bottom, to add # new steps just place them in the proper place. # rebuilddeps: This re-runs all make and make install of dependencies 3rdparty # this was needed as deleting the entire install directory an rerunning build # step for dependencies does not install if they are already built. This step # forces installation. Have not tested it lately so it might not be needed anymore # build: Runs cmake build and make step for krita sources. It always run cmake step, so # it might take a bit longer than a pure on the source tree. The script tries # to set the make flag -jN to a proper N. # install: Runs install step for krita sources. # fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by # default it fails to set a proper @rpath # buildinstall: Runs build, install and fixboost steps. if test -z $BUILDROOT; then echo "ERROR: BUILDROOT env not set, exiting!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" exit fi echo "BUILDROOT set to ${BUILDROOT}" export KIS_SRC_DIR=${BUILDROOT}/krita export KIS_TBUILD_DIR=${BUILDROOT}/depbuild export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall export KIS_DOWN_DIR=${BUILDROOT}/down export KIS_BUILD_DIR=${BUILDROOT}/kisbuild export KIS_INSTALL_DIR=${BUILDROOT}/i # flags for OSX environment # We only support from 10.11 up export MACOSX_DEPLOYMENT_TARGET=10.11 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 # Build time variables if test -z $(which cmake); then echo "ERROR: cmake not found, exiting!" exit fi export PATH=${KIS_INSTALL_DIR}/bin:$PATH export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH} export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH} export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH} # export CPPFLAGS=-I${KIS_INSTALL_DIR}/include # export LDFLAGS=-L${KIS_INSTALL_DIR}/lib # export PYTHONHOME=${KIS_INSTALL_DIR} # export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5 # This will make the debug output prettier export KDE_COLOR_DEBUG=1 export QTEST_COLORED=1 DEPBUILD_LOG="${BUILDROOT}/builddeps_.log" # configure max core for make compile ((MAKE_THREADS=1)) if test ${OSTYPE} == "darwin*"; then ((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1)) fi # Prints log to file # $2 error message # $3 success message build_errorlog () { if [[ "${1}" -ne 0 ]]; then printf "ERROR: %s\n" "$2" >> ${DEPBUILD_LOG} else printf "OK: %s\n" "$3" >> ${DEPBUILD_LOG} fi echo ${1} } check_dir_path () { printf "%s" "Checking if ${1} exists and is dir... " if test -d ${1}; then echo "OK" return 0 elif test -e ${1}; then echo "\n\tERROR: file ${1} exists but is not a directory!" return 1 else echo "Creating ${1}" mkdir ${1} fi return 0 } # builds dependencies for the first time cmake_3rdparty () { cd ${KIS_TBUILD_DIR} for package in ${@:1:${#@}}; do printf "STATUS: %s\n" "Building ${package}" >> ${DEPBUILD_LOG} cmake --build . --config RelWithDebInfo --target ${package} 2>> ${DEPBUILD_LOG} local build_error=$(build_errorlog ${?} "Failed build ${package}" "Build Success! ${package}") # run package fixes if [[ ${2} != "1" ]]; then build_3rdparty_fixes ${package} fi done } build_3rdparty_fixes(){ osxbuild_count=$((${osxbuild_count} + 1)) pkg=${1} if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5" elif [[ "${pkg}" = "ext_openexr" ]]; then # open exr will fail the first time is called # rpath needs to be fixed an build rerun echo "Fixing rpath on openexr file: b44ExpLogTable" echo "Fixing rpath on openexr file: dwaLookups" install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups # we must rerun build! cmake_3rdparty ext_openexr "1" fi } build_3rdparty () { echo "building in ${KIS_TBUILD_DIR}" check_dir_path ${KIS_TBUILD_DIR} check_dir_path ${KIS_DOWN_DIR} check_dir_path ${KIS_INSTALL_DIR} cd ${KIS_TBUILD_DIR} cmake ${KIS_SRC_DIR}/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \ -DINSTALL_ROOT=${KIS_INSTALL_DIR} # -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \ # -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib echo "finished 3rdparty build setup" # make preinstall echo "finished make step" if ! test -z ${1}; then cmake_3rdparty ${@} exit fi # build 3rdparty tools # The order must not be changed! cmake_3rdparty \ ext_pkgconfig \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_lcms2 \ ext_ocio \ ext_openexr cmake_3rdparty \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib # Stop if qmake link was not created # this meant qt build fail and further builds will # also fail. test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5" if [[ $(build_errorlog ${?} "qmake link missing!" "qmake link present, continuing...") -ne 0 ]];then printf " link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing! It probably means ext_qt failed!! check, fix and rerun!\n" exit fi # for python cmake_3rdparty \ ext_python \ ext_sip \ ext_pyqt cmake_3rdparty ext_libheif cmake_3rdparty \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } # Recall cmake for all 3rd party packages # make is only on target first run # subsequent runs only call make install rebuild_3rdparty () { echo "starting rebuild of 3rdparty packages" build_install_ext() { for pkg in ${@:1:${#@}}; do { cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp } echo "Installing ${pkg} files..." rm ${pkg}-configure ${pkg}-build ${pkg}-install - cmake_3rdparty ${pkg} 1> /dev/null + cmake_3rdparty ${pkg} >> ${BUILDROOT}/rebuilddeps_.log cd ${KIS_TBUILD_DIR} done } # Do not process complete list only pkgs given. if ! test -z ${1}; then build_install_ext ${@} exit fi build_install_ext \ ext_pkgconfig \ + ext_iconv \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ + ext_expat \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ + ext_patch \ ext_lcms2 \ ext_ocio \ + ext_ilmbase \ ext_openexr \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_python \ ext_sip \ ext_pyqt \ - build_install_ext ext_libheif + build_install_ext \ + ext_yasm \ + ext_nasm \ + ext_libx265 \ + ext_libde265 \ + ext_libheif \ # Build kde_frameworks build_install_ext \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } #not tested set_krita_dirs() { if ! test -z ${1}; then KIS_BUILD_DIR=${BUILDROOT}/b_${1} KIS_INSTALL_DIR=${BUILDROOT}/i_${1} KIS_SRC_DIR=${BUILDROOT}/src_${1} fi } # build_krita # run cmake krita build_krita () { set_krita_dirs ${1} echo ${KIS_BUILD_DIR} echo ${KIS_INSTALL_DIR} check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} cmake ${KIS_SRC_DIR} \ -DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DDEFINE_NO_DEPRECATED=1 \ -DBUILD_TESTING=OFF \ -DHIDE_SAFE_ASSERTS=ON \ -DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \ -DPYQT_SIP_DIR_OVERRIDE=$KIS_INSTALL_DIR/share/sip/ \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 # copiling phase make -j${MAKE_THREADS} # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make -j${MAKE_THREADS} fi } install_krita () { set_krita_dirs ${1} check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} make install # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make install fi } fix_boost_rpath () { set_krita_dirs ${1} # install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt if $(install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita); then echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin" fi # install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita FILES=$(find -L ${KIS_INSTALL_DIR} -name '*so' -o -name '*dylib') for FILE in $FILES; do if test -n "$(otool -L $FILE | grep boost)"; then echo "Fixing… $FILE" install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE fi done } print_usage () { echo "USAGE: osxbuild.sh [pkg]" echo "BUILDSTEPS:\t\t" echo "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg" echo "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg \t\t\t usefull for cleaning install directory and quickly reinstall all deps." echo "\n fixboost \t\t Fixes broken boost \@rpath on OSX" echo "\n build \t\t\t Builds krita" echo "\n install \t\t Installs krita" echo "\n buildinstall \t\t Build and Installs krita, running fixboost after installing" echo "" } if test ${#} -eq 0; then echo "ERROR: No option given!" print_usage exit 1 fi if test ${1} = "builddeps"; then build_3rdparty "${@:2}" elif test ${1} = "rebuilddeps"; then rebuild_3rdparty "${@:2}" elif test ${1} = "fixboost"; then fix_boost_rpath elif test ${1} = "build"; then build_krita ${2} elif test ${1} = "install"; then install_krita ${2} fix_boost_rpath ${2} elif test ${1} = "buildinstall"; then build_krita ${2} install_krita ${2} fix_boost_rpath ${2} elif test ${1} = "test"; then ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita else echo "Option ${1} not supported" print_usage fi # after finishig sometimes it complains about missing matching quotes. \ No newline at end of file diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp index 4703f5a23c..0d0f3f8ac4 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp @@ -1,253 +1,253 @@ /* * Copyright (c) 2010 Adam Celarek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software */ #include "kis_color_selector_component.h" #include "kis_color_selector_base.h" #include "KoColorSpace.h" #include #include #include KisColorSelectorComponent::KisColorSelectorComponent(KisColorSelector* parent) : QObject(parent), m_hue(0), m_hsvSaturation(1), m_value(1), m_hslSaturation(1), m_lightness(0.5), m_hsiSaturation(1), m_intensity(0.333), m_hsySaturation(1), m_luma(0.299), m_parent(parent), m_gamutMaskOn(false), m_currentGamutMask(nullptr), m_maskPreviewActive(true), m_lastX(0), m_lastY(0), m_x(0), m_y(0), m_width(0), m_height(0), m_dirty(true), m_lastColorSpace(0) { Q_ASSERT(parent); } void KisColorSelectorComponent::setGeometry(int x, int y, int width, int height) { m_x=x; m_y=y; m_width=width; m_height=height; m_dirty=true; } void KisColorSelectorComponent::paintEvent(QPainter* painter) { painter->save(); painter->translate(m_x, m_y); paint(painter); painter->restore(); m_dirty=false; m_lastColorSpace=colorSpace(); } void KisColorSelectorComponent::mouseEvent(int x, int y) { int newX=qBound(0, (x-m_x), width()); int newY=qBound(0, (y-m_y), height()); if (allowsColorSelectionAtPoint(QPoint(x, y))) { m_lastSelectedColor = selectColor(newX, newY); m_lastX=newX; m_lastY=newY; } } const KoColorSpace* KisColorSelectorComponent::colorSpace() const { const KoColorSpace* cs = m_parent->colorSpace(); Q_ASSERT(cs); return cs; } void KisColorSelectorComponent::setDirty() { m_dirty = true; setColor(m_lastSelectedColor); } void KisColorSelectorComponent::setGamutMask(KoGamutMaskSP gamutMask) { m_currentGamutMask = gamutMask; m_gamutMaskOn = true; } void KisColorSelectorComponent::unsetGamutMask() { m_gamutMaskOn = false; m_currentGamutMask = nullptr; } void KisColorSelectorComponent::updateGamutMaskPreview() { setDirty(); update(); } void KisColorSelectorComponent::toggleGamutMask(bool state) { m_gamutMaskOn = state; setDirty(); update(); } bool KisColorSelectorComponent::isDirty() const { return m_dirty || m_lastColorSpace!=colorSpace(); } bool KisColorSelectorComponent::containsPointInComponentCoords(int x, int y) const { if(x>=0 && y>=0 && x<=width() && y<=height()) return true; else return false; } -bool KisColorSelectorComponent::allowsColorSelectionAtPoint(const QPoint &pt) const +bool KisColorSelectorComponent::allowsColorSelectionAtPoint(const QPoint & /*pt*/) const { return true; } KoColor KisColorSelectorComponent::currentColor() { return selectColor(m_lastX, m_lastY); } void KisColorSelectorComponent::setParam(qreal hue, qreal hsvSaturation, qreal value, qreal hslSaturation, qreal lightness, qreal hsiSaturation, qreal intensity, qreal hsySaturation, qreal luma) { if(qFuzzyCompare(m_hue, hue) && qFuzzyCompare(m_hsvSaturation, hsvSaturation) && qFuzzyCompare(m_value, value) && qFuzzyCompare(m_hslSaturation, hslSaturation) && qFuzzyCompare(m_lightness, lightness) && qFuzzyCompare(m_hsiSaturation, hsiSaturation) && qFuzzyCompare(m_intensity, intensity) && qFuzzyCompare(m_hsySaturation, hsySaturation) && qFuzzyCompare(m_luma, luma)) return; if(hue>=0. && hue<=1.) m_hue=hue; if(hsvSaturation>=0. && hsvSaturation<=1.) { m_hsvSaturation=hsvSaturation; m_hslSaturation=-1; m_hsiSaturation=-1; m_hsySaturation=-1; } if(value>=0. && value<=1.) { m_value=value; m_intensity=-1; m_luma=-1; m_lightness=-1; } if(hslSaturation>=0. && hslSaturation<=1.) { m_hslSaturation=hslSaturation; m_hsvSaturation=-1; m_hsiSaturation=-1; m_hsySaturation=-1; } if(lightness>=0. && lightness<=1.) { m_lightness=lightness; m_value=-1; m_luma=-1; m_intensity=-1; } if(hsiSaturation>=0. && hsiSaturation<=1.) { m_hsiSaturation=hsiSaturation; m_hsvSaturation=-1; m_hslSaturation=-1; m_hsySaturation=-1; } if(intensity>=0. && intensity<=1.) { m_intensity=intensity; m_value=-1; m_luma=-1; m_lightness=-1; } if(hsySaturation>=0. && hsySaturation<=1.) { m_hsySaturation=hsySaturation; m_hsvSaturation=-1; m_hsiSaturation=-1; m_hslSaturation=-1; } if(luma>=0. && luma<=1.) { m_intensity=-1; m_value=-1; m_luma=luma; m_lightness=-1; } m_dirty=true; emit update(); } int KisColorSelectorComponent::width() const { return m_width; } int KisColorSelectorComponent::height() const { return m_height; } void KisColorSelectorComponent::setConfiguration(Parameter param, Type type) { m_parameter = param; m_type = type; } void KisColorSelectorComponent::setColor(const KoColor &color) { m_lastSelectedColor = color; } void KisColorSelectorComponent::setLastMousePosition(int x, int y) { // prevent movement due to rounding errors if (abs((int)m_lastX - x) > 1 || abs((int)m_lastY - y) > 1) { m_lastX = x; m_lastY = y; } } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_component.h b/plugins/dockers/advancedcolorselector/kis_color_selector_component.h index 7bcd8cffe4..022bcd6bf3 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_component.h +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_component.h @@ -1,131 +1,131 @@ /* * Copyright (c) 2010 Adam Celarek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software */ #ifndef KIS_COLOR_SELECTOR_COMPONENT_H #define KIS_COLOR_SELECTOR_COMPONENT_H #include #include #include #include "kis_color_selector.h" class KoColorSpace; class QPainter; class KisColorSelectorComponent : public QObject { Q_OBJECT public: typedef KisColorSelectorConfiguration::Parameters Parameter; typedef KisColorSelectorConfiguration::Type Type; explicit KisColorSelectorComponent(KisColorSelector* parent); void setGeometry(int x, int y, int width, int height); void paintEvent(QPainter*); /// saves the mouse position, so that a blip can be created. virtual void mouseEvent(int x, int y); /// return the color, that was selected by calling mouseEvent KoColor currentColor(); int width() const; int height() const; /// setConfiguration can be ignored (for instance ring and triangle, as they can have only one config) void setConfiguration(Parameter param, Type type); /// set the color, blibs etc virtual void setColor(const KoColor& color); /// force subsequent redraw of the component void setDirty(); /// returns true, if this component wants to grab the mouse (normally true, if containsPoint returns true) bool wantsGrab(int x, int y) {return containsPointInComponentCoords(x-m_x, y-m_y);} void setGamutMask(KoGamutMaskSP gamutMask); void unsetGamutMask(); void updateGamutMaskPreview(); void toggleGamutMask(bool state); public Q_SLOTS: /// set hue, saturation, value or/and lightness /// unused parameters should be set to -1 void setParam(qreal hue, qreal hsvSaturation, qreal value, qreal hslSaturation, qreal lightness, qreal hsiSaturation, qreal intensity, qreal hsySaturation, qreal luma); Q_SIGNALS: /// request for repaint, for instance, if the hue changes. void update(); /// -1, if unaffected void paramChanged(qreal hue, qreal hsvSaturation, qreal value, qreal hslSaturation, qreal lightness, qreal hsiSaturation, qreal intensity, qreal hsySaturation, qreal luma); protected: const KoColorSpace* colorSpace() const; /// returns true, if ether the colour space, the size or the parameters have changed since the last paint event bool isDirty() const; /// this method must be overloaded to return the colour at position x/y and draw a marker on that position virtual KoColor selectColor(int x, int y) = 0; /// paint component using given painter /// the component should respect width() and height() (eg. scale to width and height), but doesn't /// have to care about x/y coordinates (top left corner) virtual void paint(QPainter*) = 0; /// a subclass can implement this method, the default returns true if the coordinates are in the component rect /// values for the subclasses are provided in component coordinates, eg (0,0) is top left of component virtual bool containsPointInComponentCoords(int x, int y) const; /// a subclass can implement this method to note that the point, although it is in /// containsPointInComponentCoords area, still cannot be selected as a color (e.g. /// it is masked out). Default implementation always returns true. - virtual bool allowsColorSelectionAtPoint(const QPoint &pt) const; + virtual bool allowsColorSelectionAtPoint(const QPoint &) const; // Workaround for Bug 287001 void setLastMousePosition(int x, int y); qreal m_hue; qreal m_hsvSaturation; qreal m_value; qreal m_hslSaturation; qreal m_lightness; qreal m_hsiSaturation; qreal m_intensity; qreal m_hsySaturation; qreal m_luma; Parameter m_parameter; Type m_type; KisColorSelector* m_parent; bool m_gamutMaskOn; KoGamutMaskSP m_currentGamutMask; bool m_maskPreviewActive; qreal m_lastX; qreal m_lastY; int m_x; int m_y; private: int m_width; int m_height; bool m_dirty; const KoColorSpace* m_lastColorSpace; KoColor m_lastSelectedColor; }; #endif // KIS_COLOR_SELECTOR_COMPONENT_H diff --git a/plugins/dockers/animation/timeline_color_scheme.cpp b/plugins/dockers/animation/timeline_color_scheme.cpp index 404cdd6e0b..2c5423a15d 100644 --- a/plugins/dockers/animation/timeline_color_scheme.cpp +++ b/plugins/dockers/animation/timeline_color_scheme.cpp @@ -1,126 +1,118 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_color_scheme.h" #include #include #include #include #include #include #include "kis_debug.h" #include "krita_utils.h" #include Q_GLOBAL_STATIC(TimelineColorScheme, s_instance) -struct TimelineColorScheme::Private -{ - QColor baseColor; -}; - - TimelineColorScheme::TimelineColorScheme() - : m_d(new Private) { - m_d->baseColor = QColor(137, 192, 221); } TimelineColorScheme::~TimelineColorScheme() { } TimelineColorScheme* TimelineColorScheme::instance() { return s_instance; } QColor TimelineColorScheme::selectorColor() const { return QColor(223, 148, 51); } QColor TimelineColorScheme::selectionColor() const { //return qApp->palette().color(QPalette::Highlight); return selectorColor(); } QColor TimelineColorScheme::activeLayerBackground() const { QColor color = qApp->palette().color(QPalette::Highlight); return color; } QBrush TimelineColorScheme::headerEmpty() const { return qApp->palette().brush(QPalette::Button); } QBrush TimelineColorScheme::headerCachedFrame() const { QColor bgColor = qApp->palette().color(QPalette::Base); int darkenCoeff = bgColor.value() > 128 ? 150 : 50; return headerEmpty().color().darker(darkenCoeff); } QBrush TimelineColorScheme::headerActive() const { return selectorColor(); } QColor TimelineColorScheme::onionSkinsSliderEnabledColor() const { - return m_d->baseColor; + return qApp->palette().color(QPalette::Highlight); } QColor TimelineColorScheme::onionSkinsSliderDisabledColor() const { return qApp->palette().color(QPalette::Disabled, QPalette::HighlightedText); } QColor TimelineColorScheme::onionSkinsButtonColor() const { QColor bgColor = qApp->palette().color(QPalette::Base); const int lighterCoeff = bgColor.value() > 128 ? 120 : 80; - return m_d->baseColor.lighter(lighterCoeff); + return qApp->palette().color(QPalette::Highlight).lighter(lighterCoeff); } QFont TimelineColorScheme::getOnionSkinsFont(const QString &maxString, const QSize &availableSize) const { QFont font = qApp->font(); while(font.pointSize() > 8) { QFontMetrics fm(font); QRect rc = fm.boundingRect(maxString); if (rc.width() > availableSize.width() || rc.height() > availableSize.height()) { font.setPointSize(font.pointSize() - 1); } else { break; } } return font; } diff --git a/plugins/dockers/animation/timeline_color_scheme.h b/plugins/dockers/animation/timeline_color_scheme.h index be96729a46..19f9961014 100644 --- a/plugins/dockers/animation/timeline_color_scheme.h +++ b/plugins/dockers/animation/timeline_color_scheme.h @@ -1,57 +1,53 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_COLOR_SCHEME_H #define __TIMELINE_COLOR_SCHEME_H #include class QColor; class QBrush; class QFont; class QSize; class TimelineColorScheme { public: TimelineColorScheme(); ~TimelineColorScheme(); static TimelineColorScheme* instance(); QColor selectorColor() const; QColor selectionColor() const; QColor activeLayerBackground() const; QBrush headerEmpty() const; QBrush headerCachedFrame() const; QBrush headerActive() const; QColor onionSkinsSliderEnabledColor() const; QColor onionSkinsSliderDisabledColor() const; QColor onionSkinsButtonColor() const; QFont getOnionSkinsFont(const QString &maxString, const QSize &availableSize) const; - -private: - struct Private; - const QScopedPointer m_d; }; #endif /* __TIMELINE_COLOR_SCHEME_H */ diff --git a/plugins/dockers/histogram/histogramdockerwidget.cpp b/plugins/dockers/histogram/histogramdockerwidget.cpp index 3e2d5879a7..6612eba613 100644 --- a/plugins/dockers/histogram/histogramdockerwidget.cpp +++ b/plugins/dockers/histogram/histogramdockerwidget.cpp @@ -1,197 +1,198 @@ /* * Copyright (c) 2016 Eugene Ingerman * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "histogramdockerwidget.h" #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "kis_paint_device.h" #include "KoColorSpace.h" #include "kis_iterator_ng.h" #include "kis_canvas2.h" HistogramDockerWidget::HistogramDockerWidget(QWidget *parent, const char *name, Qt::WindowFlags f) : QLabel(parent, f), m_paintDevice(nullptr), m_smoothHistogram(true) { setObjectName(name); } HistogramDockerWidget::~HistogramDockerWidget() { } void HistogramDockerWidget::setPaintDevice(KisCanvas2* canvas) { if (canvas) { m_paintDevice = canvas->image()->projection(); m_bounds = canvas->image()->bounds(); } else { m_paintDevice.clear(); m_bounds = QRect(); m_histogramData.clear(); } } void HistogramDockerWidget::updateHistogram() { if (!m_paintDevice.isNull()) { KisPaintDeviceSP m_devClone = new KisPaintDevice(m_paintDevice->colorSpace()); m_devClone->makeCloneFrom(m_paintDevice, m_bounds); HistogramComputationThread *workerThread = new HistogramComputationThread(m_devClone, m_bounds); connect(workerThread, &HistogramComputationThread::resultReady, this, &HistogramDockerWidget::receiveNewHistogram); connect(workerThread, &HistogramComputationThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } else { m_histogramData.clear(); update(); } } void HistogramDockerWidget::receiveNewHistogram(HistVector *histogramData) { m_histogramData = *histogramData; update(); } void HistogramDockerWidget::paintEvent(QPaintEvent *event) { if (!m_histogramData.empty()) { int nBins = m_histogramData.at(0).size(); const KoColorSpace* cs = m_paintDevice->colorSpace(); QLabel::paintEvent(event); QPainter painter(this); + painter.fillRect(0, 0, this->width(), this->height(), this->palette().dark().color()); painter.setPen(this->palette().light().color()); const int NGRID = 4; for (int i = 0; i <= NGRID; ++i) { painter.drawLine(this->width()*i / NGRID, 0., this->width()*i / NGRID, this->height()); painter.drawLine(0., this->height()*i / NGRID, this->width(), this->height()*i / NGRID); } unsigned int nChannels = cs->channelCount(); QList channels = cs->channels(); unsigned int highest = 0; //find the most populous bin in the histogram to scale it properly for (int chan = 0; chan < channels.size(); chan++) { if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) { std::vector histogramTemp = m_histogramData.at(chan); //use 98th percentile, rather than max for better visual appearance int nthPercentile = 2 * histogramTemp.size() / 100; //unsigned int max = *std::max_element(m_histogramData.at(chan).begin(),m_histogramData.at(chan).end()); std::nth_element(histogramTemp.begin(), histogramTemp.begin() + nthPercentile, histogramTemp.end(), std::greater()); unsigned int max = *(histogramTemp.begin() + nthPercentile); highest = std::max(max, highest); } } painter.setWindow(QRect(-1, 0, nBins + 1, highest)); painter.setCompositionMode(QPainter::CompositionMode_Plus); for (int chan = 0; chan < (int)nChannels; chan++) { if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) { QColor color = channels.at(chan)->color(); //special handling of grayscale color spaces. can't use color returned above. if(cs->colorChannelCount()==1){ color = QColor(Qt::gray); } QColor fill_color = color; fill_color.setAlphaF(.25); painter.setBrush(fill_color); QPen pen = QPen(color); pen.setWidth(0); painter.setPen(pen); if (m_smoothHistogram) { QPainterPath path; path.moveTo(QPointF(-1, highest)); for (qint32 i = 0; i < nBins; ++i) { float v = std::max((float)highest - m_histogramData[chan][i], 0.f); path.lineTo(QPointF(i, v)); } path.lineTo(QPointF(nBins + 1, highest)); path.closeSubpath(); painter.drawPath(path); } else { pen.setWidth(1); painter.setPen(pen); for (qint32 i = 0; i < nBins; ++i) { float v = std::max((float)highest - m_histogramData[chan][i], 0.f); painter.drawLine(QPointF(i, highest), QPointF(i, v)); } } } } } } void HistogramComputationThread::run() { const KoColorSpace *cs = m_dev->colorSpace(); quint32 channelCount = m_dev->channelCount(); quint32 pixelSize = m_dev->pixelSize(); quint32 imageSize = m_bounds.width() * m_bounds.height(); quint32 nSkip = 1 + (imageSize >> 20); //for speed use about 1M pixels for computing histograms //allocate space for the histogram data bins.resize((int)channelCount); for (auto &bin : bins) { bin.resize(std::numeric_limits::max() + 1); } QRect bounds = m_dev->exactBounds(); if (bounds.isEmpty()) return; quint32 toSkip = nSkip; KisSequentialConstIterator it(m_dev, m_dev->exactBounds()); int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { numConseqPixels = it.nConseqPixels(); const quint8* pixel = it.rawDataConst(); for (int k = 0; k < numConseqPixels; ++k) { if (--toSkip == 0) { for (int chan = 0; chan < (int)channelCount; ++chan) { bins[chan][cs->scaleToU8(pixel, chan)]++; } toSkip = nSkip; } pixel += pixelSize; } } emit resultReady(&bins); } diff --git a/plugins/dockers/overview/overviewwidget.cc b/plugins/dockers/overview/overviewwidget.cc index ac662c721a..20c4bcf926 100644 --- a/plugins/dockers/overview/overviewwidget.cc +++ b/plugins/dockers/overview/overviewwidget.cc @@ -1,375 +1,381 @@ /* * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2014 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "overviewwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_idle_watcher.h" #include "krita_utils.h" #include "kis_painter.h" #include #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include #include const qreal oversample = 2.; const int thumbnailTileDim = 128; struct OverviewThumbnailStrokeStrategy::Private { class ProcessData : public KisStrokeJobData { public: ProcessData(KisPaintDeviceSP _dev, KisPaintDeviceSP _thumbDev, const QSize& _thumbnailSize, const QRect &_rect) : KisStrokeJobData(CONCURRENT), dev(_dev), thumbDev(_thumbDev), thumbnailSize(_thumbnailSize), tileRect(_rect) {} KisPaintDeviceSP dev; KisPaintDeviceSP thumbDev; QSize thumbnailSize; QRect tileRect; }; class FinishProcessing : public KisStrokeJobData { public: FinishProcessing(KisPaintDeviceSP _thumbDev, const QSize& _thumbnailSize) : KisStrokeJobData(SEQUENTIAL), thumbDev(_thumbDev), thumbnailSize(_thumbnailSize) {} KisPaintDeviceSP thumbDev; QSize thumbnailSize; }; }; OverviewWidget::OverviewWidget(QWidget * parent) : QWidget(parent) , m_canvas(0) , m_dragging(false) , m_imageIdleWatcher(250) { setMouseTracking(true); KisConfig cfg(true); - m_outlineColor = qApp->palette().color(QPalette::Highlight); + slotThemeChanged(); } OverviewWidget::~OverviewWidget() { } void OverviewWidget::setCanvas(KoCanvasBase * canvas) { if (m_canvas) { m_canvas->image()->disconnect(this); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_imageIdleWatcher.setTrackedImage(m_canvas->image()); connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &OverviewWidget::generateThumbnail); connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),SLOT(startUpdateCanvasProjection())); connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF,QPointF)),SLOT(startUpdateCanvasProjection())); connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(update()), Qt::UniqueConnection); + connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotThemeChanged())); generateThumbnail(); } } QSize OverviewWidget::recalculatePreviewSize() { QSize imageSize(m_canvas->image()->bounds().size()); const qreal hScale = 1.0 * this->width() / imageSize.width(); const qreal vScale = 1.0 * this->height() / imageSize.height(); m_previewScale = qMin(hScale, vScale); return imageSize * m_previewScale; } QPointF OverviewWidget::previewOrigin() { const QSize previewSize = recalculatePreviewSize(); return QPointF((width() - previewSize.width()) / 2.0f, (height() - previewSize.height()) / 2.0f); } QPolygonF OverviewWidget::previewPolygon() { if (m_canvas) { const QRectF &canvasRect = QRectF(m_canvas->canvasWidget()->rect()); return canvasToPreviewTransform().map(canvasRect); } return QPolygonF(); } QTransform OverviewWidget::previewToCanvasTransform() { QTransform previewToImage = QTransform::fromTranslate(-this->width() / 2.0, -this->height() / 2.0) * QTransform::fromScale(1.0 / m_previewScale, 1.0 / m_previewScale) * QTransform::fromTranslate(m_canvas->image()->width() / 2.0, m_canvas->image()->height() / 2.0); return previewToImage * m_canvas->coordinatesConverter()->imageToWidgetTransform(); } QTransform OverviewWidget::canvasToPreviewTransform() { return previewToCanvasTransform().inverted(); } void OverviewWidget::startUpdateCanvasProjection() { m_imageIdleWatcher.startCountdown(); } void OverviewWidget::showEvent(QShowEvent *event) { Q_UNUSED(event); m_imageIdleWatcher.startCountdown(); } void OverviewWidget::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); if (m_canvas) { if (!m_oldPixmap.isNull()) { QSize newSize = recalculatePreviewSize(); m_pixmap = m_oldPixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } m_imageIdleWatcher.startCountdown(); } } void OverviewWidget::mousePressEvent(QMouseEvent* event) { if (m_canvas) { QPointF previewPos = event->pos(); if (!previewPolygon().containsPoint(previewPos, Qt::WindingFill)) { const QRect& canvasRect = m_canvas->canvasWidget()->rect(); const QPointF newCanvasPos = previewToCanvasTransform().map(previewPos) - QPointF(canvasRect.width() / 2.0f, canvasRect.height() / 2.0f); m_canvas->canvasController()->pan(newCanvasPos.toPoint()); } m_lastPos = previewPos; m_dragging = true; } event->accept(); update(); } void OverviewWidget::mouseMoveEvent(QMouseEvent* event) { if (m_dragging) { QPointF previewPos = event->pos(); const QPointF lastCanvasPos = previewToCanvasTransform().map(m_lastPos); const QPointF newCanvasPos = previewToCanvasTransform().map(event->pos()); QPointF diff = newCanvasPos - lastCanvasPos; m_canvas->canvasController()->pan(diff.toPoint()); m_lastPos = previewPos; } event->accept(); } void OverviewWidget::mouseReleaseEvent(QMouseEvent* event) { m_dragging = false; event->accept(); update(); } void OverviewWidget::wheelEvent(QWheelEvent* event) { float delta = event->delta(); if (delta > 0) { m_canvas->viewManager()->zoomController()->zoomAction()->zoomIn(); } else { m_canvas->viewManager()->zoomController()->zoomAction()->zoomOut(); } } void OverviewWidget::generateThumbnail() { if (isVisible()) { QMutexLocker locker(&mutex); if (m_canvas) { QSize previewSize = recalculatePreviewSize(); if(previewSize.isValid()){ KisImageSP image = m_canvas->image(); if (!strokeId.isNull()) { image->cancelStroke(strokeId); strokeId.clear(); } OverviewThumbnailStrokeStrategy* stroke = new OverviewThumbnailStrokeStrategy(image); connect(stroke, SIGNAL(thumbnailUpdated(QImage)), this, SLOT(updateThumbnail(QImage))); strokeId = image->startStroke(stroke); KisPaintDeviceSP dev = image->projection(); KisPaintDeviceSP thumbDev = new KisPaintDevice(dev->colorSpace()); //creating a special stroke that computes thumbnail image in small chunks that can be quickly interrupted //if user starts painting QList jobs = OverviewThumbnailStrokeStrategy::createJobsData(dev, image->bounds(), thumbDev, previewSize); Q_FOREACH (KisStrokeJobData *jd, jobs) { image->addJob(strokeId, jd); } image->endStroke(strokeId); } } } } void OverviewWidget::updateThumbnail(QImage pixmap) { m_pixmap = QPixmap::fromImage(pixmap); m_oldPixmap = m_pixmap.copy(); update(); } +void OverviewWidget::slotThemeChanged() +{ + m_outlineColor = qApp->palette().color(QPalette::Highlight); +} + void OverviewWidget::paintEvent(QPaintEvent* event) { QWidget::paintEvent(event); if (m_canvas) { QPainter p(this); const QSize previewSize = recalculatePreviewSize(); const QRectF previewRect = QRectF(previewOrigin(), previewSize); p.drawPixmap(previewRect.toRect(), m_pixmap); QRect r = rect(); QPolygonF outline; outline << r.topLeft() << r.topRight() << r.bottomRight() << r.bottomLeft(); QPen pen; pen.setColor(m_outlineColor); pen.setStyle(Qt::DashLine); p.setPen(pen); p.drawPolygon(outline.intersected(previewPolygon())); pen.setStyle(Qt::SolidLine); p.setPen(pen); p.drawPolygon(previewPolygon()); } } OverviewThumbnailStrokeStrategy::OverviewThumbnailStrokeStrategy(KisImageWSP image) : KisSimpleStrokeStrategy("OverviewThumbnail"), m_image(image) { enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); //enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setRequestsOtherStrokesToEnd(false); setClearsRedoOnStart(false); setCanForgetAboutMe(true); } QList OverviewThumbnailStrokeStrategy::createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize& thumbnailSize) { QSize thumbnailOversampledSize = oversample * thumbnailSize; if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } QVector tileRects = KritaUtils::splitRectIntoPatches(QRect(QPoint(0, 0), thumbnailOversampledSize), QSize(thumbnailTileDim, thumbnailTileDim)); QList jobsData; Q_FOREACH (const QRect &tileRectangle, tileRects) { jobsData << new OverviewThumbnailStrokeStrategy::Private::ProcessData(dev, thumbDev, thumbnailOversampledSize, tileRectangle); } jobsData << new OverviewThumbnailStrokeStrategy::Private::FinishProcessing(thumbDev, thumbnailSize); return jobsData; } OverviewThumbnailStrokeStrategy::~OverviewThumbnailStrokeStrategy() { } void OverviewThumbnailStrokeStrategy::initStrokeCallback() { } void OverviewThumbnailStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { Private::ProcessData *d_pd = dynamic_cast(data); if (d_pd) { //we aren't going to use oversample capability of createThumbnailDevice because it recomputes exact bounds for each small patch, which is //slow. We'll handle scaling separately. KisPaintDeviceSP thumbnailTile = d_pd->dev->createThumbnailDeviceOversampled(d_pd->thumbnailSize.width(), d_pd->thumbnailSize.height(), 1, m_image->bounds(), d_pd->tileRect); { QMutexLocker locker(&m_thumbnailMergeMutex); KisPainter gc(d_pd->thumbDev); gc.bitBlt(QPoint(d_pd->tileRect.x(), d_pd->tileRect.y()), thumbnailTile, d_pd->tileRect); } return; } Private::FinishProcessing *d_fp = dynamic_cast(data); if (d_fp) { QImage overviewImage; KoDummyUpdater updater; KisTransformWorker worker(d_fp->thumbDev, 1 / oversample, 1 / oversample, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); overviewImage = d_fp->thumbDev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), QRect(QPoint(0,0), d_fp->thumbnailSize)); emit thumbnailUpdated(overviewImage); return; } } void OverviewThumbnailStrokeStrategy::finishStrokeCallback() { } void OverviewThumbnailStrokeStrategy::cancelStrokeCallback() { } diff --git a/plugins/dockers/overview/overviewwidget.h b/plugins/dockers/overview/overviewwidget.h index 1403500206..ff69aba503 100644 --- a/plugins/dockers/overview/overviewwidget.h +++ b/plugins/dockers/overview/overviewwidget.h @@ -1,116 +1,117 @@ /* * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2014 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OVERVIEWWIDGET_H #define OVERVIEWWIDGET_H #include #include #include #include #include #include "kis_idle_watcher.h" #include "kis_simple_stroke_strategy.h" #include class KisSignalCompressor; class KoCanvasBase; class OverviewThumbnailStrokeStrategy : public QObject, public KisSimpleStrokeStrategy { Q_OBJECT public: OverviewThumbnailStrokeStrategy(KisImageWSP image); ~OverviewThumbnailStrokeStrategy() override; static QList createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize &thumbnailSize); private: void initStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; void finishStrokeCallback() override; void cancelStrokeCallback() override; Q_SIGNALS: //Emitted when thumbnail is updated and overviewImage is fully generated. void thumbnailUpdated(QImage pixmap); private: struct Private; const QScopedPointer m_d; QMutex m_thumbnailMergeMutex; KisImageSP m_image; }; class OverviewWidget : public QWidget { Q_OBJECT public: OverviewWidget(QWidget * parent = 0); ~OverviewWidget() override; virtual void setCanvas(KoCanvasBase *canvas); virtual void unsetCanvas() { m_canvas = 0; } public Q_SLOTS: void startUpdateCanvasProjection(); void generateThumbnail(); void updateThumbnail(QImage pixmap); + void slotThemeChanged(); protected: void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void wheelEvent(QWheelEvent* event) override; private: QSize recalculatePreviewSize(); QPointF previewOrigin(); QTransform canvasToPreviewTransform(); QTransform previewToCanvasTransform(); QPolygonF previewPolygon(); qreal m_previewScale; QPixmap m_oldPixmap; QPixmap m_pixmap; QPointer m_canvas; bool m_dragging; QPointF m_lastPos; QColor m_outlineColor; KisIdleWatcher m_imageIdleWatcher; KisStrokeId strokeId; QMutex mutex; }; #endif /* OVERVIEWWIDGET_H */ diff --git a/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp b/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp index 72114d01c7..11e5b9debb 100644 --- a/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp +++ b/plugins/dockers/smallcolorselector/KisGLImageWidget.cpp @@ -1,283 +1,285 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisGLImageWidget.h" #include #include #include #include "kis_debug.h" #include #include #include "KisGLImageF16.h" namespace { inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } } KisGLImageWidget::KisGLImageWidget(QWidget *parent) : KisGLImageWidget(KisSurfaceColorSpace::sRGBColorSpace, parent) { } KisGLImageWidget::KisGLImageWidget(KisSurfaceColorSpace colorSpace, QWidget *parent) : QOpenGLWidget(parent), m_texture(QOpenGLTexture::Target2D) { + Q_UNUSED(colorSpace) + #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) setTextureFormat(GL_RGBA16F); #endif #ifdef HAVE_HDR setTextureColorSpace(colorSpace); #endif setUpdateBehavior(QOpenGLWidget::NoPartialUpdate); } KisGLImageWidget::~KisGLImageWidget() { // force releasing the reourses on destruction slotOpenGLContextDestroyed(); } void KisGLImageWidget::initializeGL() { initializeOpenGLFunctions(); connect(context(), SIGNAL(aboutToBeDestroyed()), SLOT(slotOpenGLContextDestroyed())); m_shader.reset(new QOpenGLShaderProgram); QFile vertexShaderFile(QString(":/") + "kis_gl_image_widget.vert"); vertexShaderFile.open(QIODevice::ReadOnly); QString vertSource = vertexShaderFile.readAll(); QFile fragShaderFile(QString(":/") + "kis_gl_image_widget.frag"); fragShaderFile.open(QIODevice::ReadOnly); QString fragSource = fragShaderFile.readAll(); if (context()->isOpenGLES()) { const char *versionHelper = "#define USE_OPENGLES\n"; vertSource.prepend(versionHelper); fragSource.prepend(versionHelper); const char *versionDefinition = "#version 100\n"; vertSource.prepend(versionDefinition); fragSource.prepend(versionDefinition); } else { const char *versionDefinition = KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n"; vertSource.prepend(versionDefinition); fragSource.prepend(versionDefinition); } if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource)) { qDebug() << "Could not add vertex code"; return; } if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource)) { qDebug() << "Could not add fragment code"; return; } if (!m_shader->link()) { qDebug() << "Could not link"; return; } if (!m_shader->bind()) { qDebug() << "Could not bind"; return; } m_shader->release(); m_vao.create(); m_vao.bind(); m_verticesBuffer.create(); updateVerticesBuffer(this->rect()); QVector textureVertices(6); rectToTexCoords(textureVertices.data(), QRect(0.0, 0.0, 1.0, 1.0)); m_textureVerticesBuffer.create(); m_textureVerticesBuffer.bind(); m_textureVerticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); m_textureVerticesBuffer.allocate(2 * 3 * sizeof(QVector2D)); m_verticesBuffer.write(0, textureVertices.data(), m_textureVerticesBuffer.size()); m_textureVerticesBuffer.release(); m_vao.release(); if (!m_sourceImage.isNull()) { loadImage(m_sourceImage); } } void KisGLImageWidget::slotOpenGLContextDestroyed() { this->makeCurrent(); m_shader.reset(); m_texture.destroy(); m_verticesBuffer.destroy(); m_textureVerticesBuffer.destroy(); m_vao.destroy(); m_havePendingTextureUpdate = false; this->doneCurrent(); } void KisGLImageWidget::updateVerticesBuffer(const QRect &rect) { if (!m_vao.isCreated() || !m_verticesBuffer.isCreated()) return; QVector vertices(6); rectToVertices(vertices.data(), rect); m_verticesBuffer.bind(); m_verticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); m_verticesBuffer.allocate(2 * 3 * sizeof(QVector3D)); m_verticesBuffer.write(0, vertices.data(), m_verticesBuffer.size()); m_verticesBuffer.release(); } void KisGLImageWidget::paintGL() { - const QColor bgColor = palette().background().color(); // TODO: fix conversion to the destination surface space - //glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f); + // Fill with bright color as as default for debugging purposes + // glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f); glClearColor(0.3, 0.2, 0.8, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (m_havePendingTextureUpdate) { m_havePendingTextureUpdate = false; if (!m_texture.isCreated() || m_sourceImage.width() != m_texture.width() || m_sourceImage.height() != m_texture.height()) { if (m_texture.isCreated()) { m_texture.destroy(); } m_texture.setFormat(QOpenGLTexture::RGBA16F); m_texture.setSize(m_sourceImage.width(), m_sourceImage.height()); m_texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::Float16); m_texture.setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); m_texture.setMagnificationFilter(QOpenGLTexture::Linear); m_texture.setWrapMode(QOpenGLTexture::ClampToEdge); } m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::Float16, m_sourceImage.constData()); } if (!m_texture.isCreated()) return; m_vao.bind(); m_shader->bind(); { QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, -1, 1); QMatrix4x4 viewProjectionMatrix; // use a QTransform to scale, translate, rotate your view QTransform transform; // TODO: noop! viewProjectionMatrix = projectionMatrix * QMatrix4x4(transform); m_shader->setUniformValue("viewProjectionMatrix", viewProjectionMatrix); } m_shader->enableAttributeArray("vertexPosition"); m_verticesBuffer.bind(); m_shader->setAttributeBuffer("vertexPosition", GL_FLOAT, 0, 3); m_shader->enableAttributeArray("texturePosition"); m_textureVerticesBuffer.bind(); m_shader->setAttributeBuffer("texturePosition", GL_FLOAT, 0, 2); glActiveTexture(GL_TEXTURE0); m_texture.bind(); // draw 2 triangles = 6 vertices starting at offset 0 in the buffer glDrawArrays(GL_TRIANGLES, 0, 6); m_verticesBuffer.release(); m_textureVerticesBuffer.release(); m_texture.release(); m_shader->release(); m_vao.release(); } void KisGLImageWidget::loadImage(const KisGLImageF16 &image) { if (m_sourceImage != image) { m_sourceImage = image; } m_havePendingTextureUpdate = true; updateGeometry(); update(); } void KisGLImageWidget::paintEvent(QPaintEvent *event) { QOpenGLWidget::paintEvent(event); } void KisGLImageWidget::resizeEvent(QResizeEvent *event) { updateVerticesBuffer(QRect(QPoint(),event->size())); QOpenGLWidget::resizeEvent(event); } QSize KisGLImageWidget::sizeHint() const { return m_sourceImage.size(); } diff --git a/plugins/dockers/smallcolorselector/kis_small_color_widget.cc b/plugins/dockers/smallcolorselector/kis_small_color_widget.cc index 0f9d586bea..db47426e6d 100644 --- a/plugins/dockers/smallcolorselector/kis_small_color_widget.cc +++ b/plugins/dockers/smallcolorselector/kis_small_color_widget.cc @@ -1,516 +1,516 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_small_color_widget.h" #include #include "kis_slider_spin_box.h" #include #include "kis_signal_compressor.h" #include #include #include #include "kis_debug.h" #include "kis_assert.h" #include #include "KisGLImageF16.h" #include "KisGLImageWidget.h" #include "KisClickableGLImageWidget.h" #include "kis_display_color_converter.h" #include "kis_signal_auto_connection.h" #include "kis_signal_compressor_with_param.h" #include #include #include "kis_fixed_paint_device.h" #include struct KisSmallColorWidget::Private { qreal hue; // 0 ... 1.0 qreal value; // 0 ... 1.0 qreal saturation; // 0 ... 1.0 bool updateAllowed; KisClickableGLImageWidget *hueWidget; KisClickableGLImageWidget *valueWidget; KisSignalCompressor *repaintCompressor; KisSignalCompressor *resizeUpdateCompressor; KisSignalCompressor *valueSliderUpdateCompressor; KisSignalCompressor *colorChangedSignalCompressor; KisSignalCompressorWithParam *dynamicRangeCompressor; int huePreferredHeight = 32; KisSliderSpinBox *dynamicRange = 0; qreal currentRelativeDynamicRange = 1.0; KisDisplayColorConverter *displayColorConverter = KisDisplayColorConverter::dumbConverterInstance(); KisSignalAutoConnectionsStore colorConverterConnections; bool hasHDR = false; bool hasHardwareHDR = false; qreal effectiveRelativeDynamicRange() const { return hasHDR ? currentRelativeDynamicRange : 1.0; } const KoColorSpace *outputColorSpace() { return KoColorSpaceRegistry::instance()-> colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), displayColorConverter->openGLCanvasSurfaceProfile()); } const KoColorSpace *generationColorSpace() { const KoColorSpace *result = displayColorConverter->paintingColorSpace(); if (!result || result->colorModelId() != RGBAColorModelID) { result = outputColorSpace(); } else if (result->colorDepthId() != Float32BitsColorDepthID) { result = KoColorSpaceRegistry::instance()-> colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), result->profile()); } // PQ color space we deliniearize into linear one if (result->colorModelId() == RGBAColorModelID && result->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId()) { result = KoColorSpaceRegistry::instance()-> colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020G10Profile()); } return result; } }; KisSmallColorWidget::KisSmallColorWidget(QWidget* parent) : QWidget(parent), d(new Private) { d->hue = 0.0; d->value = 0; d->saturation = 0; d->updateAllowed = true; d->repaintCompressor = new KisSignalCompressor(20, KisSignalCompressor::FIRST_ACTIVE, this); connect(d->repaintCompressor, SIGNAL(timeout()), SLOT(update())); d->resizeUpdateCompressor = new KisSignalCompressor(200, KisSignalCompressor::FIRST_ACTIVE, this); connect(d->resizeUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdatePalettes())); d->valueSliderUpdateCompressor = new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE, this); connect(d->valueSliderUpdateCompressor, SIGNAL(timeout()), SLOT(updateSVPalette())); d->colorChangedSignalCompressor = new KisSignalCompressor(20, KisSignalCompressor::FIRST_ACTIVE, this); connect(d->colorChangedSignalCompressor, SIGNAL(timeout()), SLOT(slotTellColorChanged())); { using namespace std::placeholders; std::function callback( std::bind(&KisSmallColorWidget::updateDynamicRange, this, _1)); d->dynamicRangeCompressor = new KisSignalCompressorWithParam(50, callback); } const KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace; d->hueWidget = new KisClickableGLImageWidget(colorSpace, this); d->hueWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); d->hueWidget->setHandlePaintingStrategy(new KisClickableGLImageWidget::VerticalLineHandleStrategy); connect(d->hueWidget, SIGNAL(selected(const QPointF&)), SLOT(slotHueSliderChanged(const QPointF&))); d->valueWidget = new KisClickableGLImageWidget(colorSpace, this); d->valueWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); d->valueWidget->setHandlePaintingStrategy(new KisClickableGLImageWidget::CircularHandleStrategy); connect(d->valueWidget, SIGNAL(selected(const QPointF&)), SLOT(slotValueSliderChanged(const QPointF&))); d->hasHardwareHDR = KisOpenGLModeProber::instance()->useHDRMode(); if (d->hasHardwareHDR) { d->dynamicRange = new KisSliderSpinBox(this); d->dynamicRange->setRange(80, 10000); d->dynamicRange->setExponentRatio(3.0); d->dynamicRange->setSingleStep(1); d->dynamicRange->setPageStep(100); d->dynamicRange->setSuffix("cd/m²"); d->dynamicRange->setValue(80.0 * d->currentRelativeDynamicRange); connect(d->dynamicRange, SIGNAL(valueChanged(int)), SLOT(slotInitiateUpdateDynamicRange(int))); } QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(d->hueWidget, 0); layout->addWidget(d->valueWidget, 1); if (d->dynamicRange) { layout->addSpacing(16); layout->addWidget(d->dynamicRange, 0); } setLayout(layout); slotUpdatePalettes(); } KisSmallColorWidget::~KisSmallColorWidget() { delete d; } void KisSmallColorWidget::setHue(qreal h) { h = qBound(0.0, h, 1.0); d->hue = h; d->colorChangedSignalCompressor->start(); d->valueSliderUpdateCompressor->start(); d->repaintCompressor->start(); } void KisSmallColorWidget::setHSV(qreal h, qreal s, qreal v, bool notifyChanged) { h = qBound(0.0, h, 1.0); s = qBound(0.0, s, 1.0); v = qBound(0.0, v, 1.0); bool newH = !qFuzzyCompare(d->hue, h); d->hue = h; d->value = v; d->saturation = s; // TODO: remove and make acyclic! if (notifyChanged) { d->colorChangedSignalCompressor->start(); } if(newH) { d->valueSliderUpdateCompressor->start(); } d->repaintCompressor->start(); } void KisSmallColorWidget::setColor(const KoColor &color) { if (!d->updateAllowed) return; KIS_SAFE_ASSERT_RECOVER(!d->dynamicRange || d->hasHDR == d->dynamicRange->isEnabled()) { slotDisplayConfigurationChanged(); } KIS_SAFE_ASSERT_RECOVER_RETURN(!d->hasHDR || d->hasHardwareHDR); const KoColorSpace *cs = d->generationColorSpace(); KIS_SAFE_ASSERT_RECOVER_RETURN(cs); KoColor newColor(color); newColor.convertTo(cs); QVector channels(4); cs->normalisedChannelsValue(newColor.data(), channels); float r, g, b; if (cs->colorDepthId() == Integer8BitsColorDepthID) { r = channels[2]; g = channels[1]; b = channels[0]; } else { r = channels[0]; g = channels[1]; b = channels[2]; } if (d->hasHDR) { qreal rangeCoeff = d->effectiveRelativeDynamicRange(); if (rangeCoeff < r || rangeCoeff < g || rangeCoeff < b) { rangeCoeff = std::max({r, g, b}) * 1.10f; const int newMaxLuminance = qRound(80.0 * rangeCoeff); updateDynamicRange(newMaxLuminance); d->dynamicRange->setValue(newMaxLuminance); } r /= rangeCoeff; g /= rangeCoeff; b /= rangeCoeff; } else { r = qBound(0.0f, r, 1.0f); g = qBound(0.0f, g, 1.0f); b = qBound(0.0f, b, 1.0f); } float denormHue, saturation, value; RGBToHSV(r, g, b, &denormHue, &saturation, &value); d->hueWidget->setNormalizedPos(QPointF(denormHue / 360.0, 0.0)); d->valueWidget->setNormalizedPos(QPointF(saturation, 1.0 - value)); setHSV(denormHue / 360.0, saturation, value, false); } void KisSmallColorWidget::slotUpdatePalettes() { updateHuePalette(); updateSVPalette(); } namespace { struct FillHPolicy { - static inline void getRGB(qreal hue, float xPortionCoeff, float yPortionCoeff, - int x, int y, float *r, float *g, float *b) { + static inline void getRGB(qreal /*hue*/, float xPortionCoeff, float /*yPortionCoeff*/, + int x, int /*y*/, float *r, float *g, float *b) { HSVToRGB(xPortionCoeff * x * 360.0f, 1.0, 1.0, r, g, b); } }; struct FillSVPolicy { static inline void getRGB(qreal hue, float xPortionCoeff, float yPortionCoeff, int x, int y, float *r, float *g, float *b) { HSVToRGB(hue * 360.0, xPortionCoeff * x, 1.0 - yPortionCoeff * y, r, g, b); } }; } template void KisSmallColorWidget::uploadPaletteData(KisGLImageWidget *widget, const QSize &size) { if (size.isEmpty()) return; KisGLImageF16 image(size); const float xPortionCoeff = 1.0 / image.width(); const float yPortionCoeff = 1.0 / image.height(); const float rangeCoeff = d->effectiveRelativeDynamicRange(); const KoColorSpace *generationColorSpace = d->generationColorSpace(); if (d->displayColorConverter->canSkipDisplayConversion(generationColorSpace)) { half *pixelPtr = image.data(); for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { Imf::Rgba &pxl = reinterpret_cast(*pixelPtr); float r, g, b; FillPolicy::getRGB(d->hue, xPortionCoeff, yPortionCoeff, x, y, &r, &g, &b); pxl.r = r * rangeCoeff; pxl.g = g * rangeCoeff; pxl.b = b * rangeCoeff; pxl.a = 1.0; pixelPtr += 4; } } } else { KIS_SAFE_ASSERT_RECOVER_RETURN(d->displayColorConverter); KisFixedPaintDeviceSP device = new KisFixedPaintDevice(generationColorSpace); device->setRect(QRect(QPoint(), image.size())); device->reallocateBufferWithoutInitialization(); float *devicePtr = reinterpret_cast(device->data()); for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { FillPolicy::getRGB(d->hue, xPortionCoeff, yPortionCoeff, x, y, devicePtr, devicePtr + 1, devicePtr + 2); devicePtr[0] *= rangeCoeff; devicePtr[1] *= rangeCoeff; devicePtr[2] *= rangeCoeff; devicePtr[3] = 1.0; devicePtr += 4; } } d->displayColorConverter->applyDisplayFilteringF32(device, Float32BitsColorDepthID); half *imagePtr = image.data(); devicePtr = reinterpret_cast(device->data()); for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { imagePtr[0] = devicePtr[0]; imagePtr[1] = devicePtr[1]; imagePtr[2] = devicePtr[2]; imagePtr[3] = devicePtr[3]; devicePtr += 4; imagePtr += 4; } } } widget->loadImage(image); } void KisSmallColorWidget::updateHuePalette() { uploadPaletteData(d->hueWidget, QSize(d->hueWidget->width(), d->huePreferredHeight)); } void KisSmallColorWidget::updateSVPalette() { const int maxSize = 256; QSize newSize = d->valueWidget->size(); newSize.rwidth() = qMin(maxSize, newSize.width()); newSize.rheight() = qMin(maxSize, newSize.height()); uploadPaletteData(d->valueWidget, newSize); } void KisSmallColorWidget::slotHueSliderChanged(const QPointF &pos) { const qreal newHue = pos.x(); if (!qFuzzyCompare(newHue, d->hue)) { setHue(newHue); } } void KisSmallColorWidget::slotValueSliderChanged(const QPointF &pos) { const qreal newSaturation = pos.x(); const qreal newValue = 1.0 - pos.y(); if (!qFuzzyCompare(newSaturation, d->saturation) || !qFuzzyCompare(newValue, d->value)) { setHSV(d->hue, newSaturation, newValue); } } void KisSmallColorWidget::slotInitiateUpdateDynamicRange(int maxLuminance) { d->dynamicRangeCompressor->start(maxLuminance); } void KisSmallColorWidget::updateDynamicRange(int maxLuminance) { const qreal oldRange = d->currentRelativeDynamicRange; const qreal newRange = qreal(maxLuminance) / 80.0; if (qFuzzyCompare(oldRange, newRange)) return; float r, g, b; float denormHue = d->hue * 360.0; float saturation = d->saturation; float value = d->value; HSVToRGB(denormHue, saturation, value, &r, &g, &b); const qreal transformCoeff = oldRange / newRange; r = qBound(0.0, r * transformCoeff, 1.0); g = qBound(0.0, g * transformCoeff, 1.0); b = qBound(0.0, b * transformCoeff, 1.0); RGBToHSV(r, g, b, &denormHue, &saturation, &value); d->currentRelativeDynamicRange = newRange; slotUpdatePalettes(); setHSV(denormHue / 360.0, saturation, value, false); d->hueWidget->setNormalizedPos(QPointF(denormHue / 360.0, 0)); d->valueWidget->setNormalizedPos(QPointF(saturation, 1.0 - value)); } void KisSmallColorWidget::setDisplayColorConverter(KisDisplayColorConverter *converter) { d->colorConverterConnections.clear(); if (!converter) { converter = KisDisplayColorConverter::dumbConverterInstance(); } d->displayColorConverter = converter; if (d->displayColorConverter) { d->colorConverterConnections.addConnection( d->displayColorConverter, SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged())); } slotDisplayConfigurationChanged(); } void KisSmallColorWidget::slotDisplayConfigurationChanged() { d->hasHDR = false; if (d->hasHardwareHDR) { const KoColorSpace *cs = d->displayColorConverter->paintingColorSpace(); d->hasHDR = cs->colorModelId() == RGBAColorModelID && (cs->colorDepthId() == Float16BitsColorDepthID || cs->colorDepthId() == Float32BitsColorDepthID || cs->colorDepthId() == Float64BitsColorDepthID || cs->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId()); } if (d->dynamicRange) { d->dynamicRange->setEnabled(d->hasHDR); } d->hueWidget->setUseHandleOpacity(!d->hasHDR); d->valueWidget->setUseHandleOpacity(!d->hasHDR); slotUpdatePalettes(); // TODO: also set the currently selected color again } void KisSmallColorWidget::slotTellColorChanged() { d->updateAllowed = false; float r, g, b; HSVToRGB(d->hue * 360.0, d->saturation, d->value, &r, &g, &b); if (d->hasHDR) { const float rangeCoeff = d->effectiveRelativeDynamicRange(); r *= rangeCoeff; g *= rangeCoeff; b *= rangeCoeff; } const KoColorSpace *cs = d->generationColorSpace(); KIS_SAFE_ASSERT_RECOVER_RETURN(cs); QVector values(4); if (cs->colorDepthId() == Integer8BitsColorDepthID) { values[0] = b; values[1] = g; values[2] = r; values[3] = 1.0f; } else { values[0] = r; values[1] = g; values[2] = b; values[3] = 1.0f; } KoColor c(cs); cs->fromNormalisedChannelsValue(c.data(), values); emit colorChanged(c); d->updateAllowed = true; } void KisSmallColorWidget::resizeEvent(QResizeEvent * event) { QWidget::resizeEvent(event); update(); d->resizeUpdateCompressor->start(); } diff --git a/plugins/filters/fastcolortransfer/fastcolortransfer.cpp b/plugins/filters/fastcolortransfer/fastcolortransfer.cpp index afdd17a703..e08986abb7 100644 --- a/plugins/filters/fastcolortransfer/fastcolortransfer.cpp +++ b/plugins/filters/fastcolortransfer/fastcolortransfer.cpp @@ -1,173 +1,171 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_fastcolortransfer.h" #include "ui_wdgfastcolortransfer.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaFastColorTransferFactory, "kritafastcolortransfer.json", registerPlugin();) FastColorTransferPlugin::FastColorTransferPlugin(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterFastColorTransfer()); } FastColorTransferPlugin::~FastColorTransferPlugin() { } KisFilterFastColorTransfer::KisFilterFastColorTransfer() : KisFilter(id(), FiltersCategoryColorId, i18n("&Color Transfer...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsThreading(false); setSupportsPainting(false); setSupportsAdjustmentLayers(false); } KisConfigWidget * KisFilterFastColorTransfer::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const { Q_UNUSED(dev); return new KisWdgFastColorTransfer(parent); } KisFilterConfigurationSP KisFilterFastColorTransfer::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); config->setProperty("filename", ""); return config; } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) void KisFilterFastColorTransfer::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { Q_ASSERT(device != 0); dbgPlugins << "Start transferring color"; // Convert ref and src to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; return; } dbgPlugins << "convert a copy of src to lab"; const KoColorSpace* oldCS = device->colorSpace(); KisPaintDeviceSP srcLAB = new KisPaintDevice(*device.data()); dbgPlugins << "srcLab : " << srcLAB->extent(); - KUndo2Command* cmd = srcLAB->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - delete cmd; - + srcLAB->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KoProgressUpdater compositeUpdater(progressUpdater, KoProgressUpdater::Unthreaded); KoUpdater *updaterStats = compositeUpdater.startSubtask(1); KoUpdater *updaterMap = compositeUpdater.startSubtask(2); // Compute the means and sigmas of src dbgPlugins << "Compute the means and sigmas of src"; double meanL_src = 0., meanA_src = 0., meanB_src = 0.; double sigmaL_src = 0., sigmaA_src = 0., sigmaB_src = 0.; { KisSequentialConstIteratorProgress srcIt(srcLAB, applyRect, updaterStats); while (srcIt.nextPixel()) { const quint16* data = reinterpret_cast(srcIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_src += L; meanA_src += A; meanB_src += B; sigmaL_src += L * L; sigmaA_src += A * A; sigmaB_src += B * B; } } double totalSize = 1. / (applyRect.width() * applyRect.height()); meanL_src *= totalSize; meanA_src *= totalSize; meanB_src *= totalSize; sigmaL_src *= totalSize; sigmaA_src *= totalSize; sigmaB_src *= totalSize; dbgPlugins << totalSize << "" << meanL_src << "" << meanA_src << "" << meanB_src << "" << sigmaL_src << "" << sigmaA_src << "" << sigmaB_src; double meanL_ref = config->getDouble("meanL"); double meanA_ref = config->getDouble("meanA"); double meanB_ref = config->getDouble("meanB"); double sigmaL_ref = config->getDouble("sigmaL"); double sigmaA_ref = config->getDouble("sigmaA"); double sigmaB_ref = config->getDouble("sigmaB"); // Transfer colors dbgPlugins << "Transfer colors"; { double coefL = sqrt((sigmaL_ref - meanL_ref * meanL_ref) / (sigmaL_src - meanL_src * meanL_src)); double coefA = sqrt((sigmaA_ref - meanA_ref * meanA_ref) / (sigmaA_src - meanA_src * meanA_src)); double coefB = sqrt((sigmaB_ref - meanB_ref * meanB_ref) / (sigmaB_src - meanB_src * meanB_src)); quint16 labPixel[4]; KisSequentialConstIteratorProgress srcLabIt(srcLAB, applyRect, updaterMap); KisSequentialIterator dstIt(device, applyRect); while (srcLabIt.nextPixel() && dstIt.nextPixel()) { const quint16* data = reinterpret_cast(srcLabIt.oldRawData()); labPixel[0] = (quint16)CLAMP(((double)data[0] - meanL_src) * coefL + meanL_ref, 0., 65535.); labPixel[1] = (quint16)CLAMP(((double)data[1] - meanA_src) * coefA + meanA_ref, 0., 65535.); labPixel[2] = (quint16)CLAMP(((double)data[2] - meanB_src) * coefB + meanB_ref, 0., 65535.); labPixel[3] = data[3]; oldCS->fromLabA16(reinterpret_cast(labPixel), dstIt.rawData(), 1); } } } #include "fastcolortransfer.moc" diff --git a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp index 596d5f7715..1e2049502b 100644 --- a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp +++ b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp @@ -1,145 +1,144 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_wdg_fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgfastcolortransfer.h" KisWdgFastColorTransfer::KisWdgFastColorTransfer(QWidget * parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgFastColorTransfer(); m_widget->setupUi(this); m_widget->fileNameURLRequester->setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); connect(m_widget->fileNameURLRequester, SIGNAL(textChanged(QString)), this, SIGNAL(sigConfigurationItemChanged())); } KisWdgFastColorTransfer::~KisWdgFastColorTransfer() { delete m_widget; } void KisWdgFastColorTransfer::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("filename", value)) { widget()->fileNameURLRequester->setFileName(value.toString()); } } KisPropertiesConfigurationSP KisWdgFastColorTransfer::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("colortransfer", 1); QString fileName = this->widget()->fileNameURLRequester->fileName(); if (fileName.isEmpty()) return config; KisPaintDeviceSP ref; dbgPlugins << "Use as reference file : " << fileName; KisDocument *d = KisPart::instance()->createDocument(); KisImportExportManager manager(d); KisImportExportFilter::ConversionStatus status = manager.importDocument(fileName, QString()); dbgPlugins << "import returned status" << status; KisImageWSP importedImage = d->image(); if (importedImage) { ref = importedImage->projection(); } if (!ref) { dbgPlugins << "No reference image was specified."; delete d; return config; } // Convert ref to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; delete d; return config; } dbgPlugins << "convert ref to lab"; - KUndo2Command* cmd = ref->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - delete cmd; + ref->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); // Compute the means and sigmas of ref double meanL_ref = 0., meanA_ref = 0., meanB_ref = 0.; double sigmaL_ref = 0., sigmaA_ref = 0., sigmaB_ref = 0.; KisSequentialConstIterator refIt(ref, importedImage->bounds()); while (refIt.nextPixel()) { const quint16* data = reinterpret_cast(refIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_ref += L; meanA_ref += A; meanB_ref += B; sigmaL_ref += L * L; sigmaA_ref += A * A; sigmaB_ref += B * B; } double totalSize = 1. / (importedImage->width() * importedImage->height()); meanL_ref *= totalSize; meanA_ref *= totalSize; meanB_ref *= totalSize; sigmaL_ref *= totalSize; sigmaA_ref *= totalSize; sigmaB_ref *= totalSize; dbgPlugins << totalSize << "" << meanL_ref << "" << meanA_ref << "" << meanB_ref << "" << sigmaL_ref << "" << sigmaA_ref << "" << sigmaB_ref; config->setProperty("filename", fileName); config->setProperty("meanL", meanL_ref); config->setProperty("meanA", meanA_ref); config->setProperty("meanB", meanB_ref); config->setProperty("sigmaL", sigmaL_ref); config->setProperty("sigmaA", sigmaA_ref); config->setProperty("sigmaB", sigmaB_ref); delete d; return config; } diff --git a/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp b/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp index d3bae8c93e..3c2b42c272 100644 --- a/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp +++ b/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp @@ -1,239 +1,238 @@ /* * Copyright (c) 2010-2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_phong_bumpmap_filter.h" #include "kis_phong_bumpmap_config_widget.h" #include "phong_pixel_processor.h" #include "kis_debug.h" #include "kis_paint_device.h" #include "kis_config_widget.h" #include "KoUpdater.h" #include "kis_math_toolbox.h" #include "KoColorSpaceRegistry.h" #include #include #include #include "kis_iterator_ng.h" #include "kundo2command.h" #include "kis_painter.h" KisFilterPhongBumpmap::KisFilterPhongBumpmap() : KisFilter(KoID("phongbumpmap", i18n("Phong Bumpmap")), FiltersCategoryMapId, i18n("&Phong Bumpmap...")) { setColorSpaceIndependence(TO_LAB16); setSupportsPainting(true); setSupportsLevelOfDetail(true); } void KisFilterPhongBumpmap::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater ) const { if (!config) return; if (progressUpdater) progressUpdater->setProgress(0); QString userChosenHeightChannel = config->getString(PHONG_HEIGHT_CHANNEL, "FAIL"); bool m_usenormalmap = config->getBool(USE_NORMALMAP_IS_ENABLED); if (userChosenHeightChannel == "FAIL") { qDebug("FIX YOUR FILTER"); return; } KoChannelInfo *m_heightChannel = 0; Q_FOREACH (KoChannelInfo* channel, device->colorSpace()->channels()) { if (userChosenHeightChannel == channel->name()) { m_heightChannel = channel; } } if (!m_heightChannel) { m_heightChannel = device->colorSpace()->channels().first(); } KIS_ASSERT_RECOVER_RETURN(m_heightChannel); QRect inputArea = applyRect; QRect outputArea = applyRect; if (m_usenormalmap==false) { inputArea.adjust(-1, -1, 1, 1); } quint32 posup; quint32 posdown; quint32 posleft; quint32 posright; QColor I; //Reflected light if (progressUpdater) progressUpdater->setProgress(1); //======Preparation paraphlenalia======= //Hardcoded facts about Phong Bumpmap: it _will_ generate an RGBA16 bumpmap const quint8 BYTE_DEPTH_OF_BUMPMAP = 2; // 16 bits per channel const quint8 CHANNEL_COUNT_OF_BUMPMAP = 4; // RGBA const quint32 pixelsOfInputArea = abs(inputArea.width() * inputArea.height()); const quint32 pixelsOfOutputArea = abs(outputArea.width() * outputArea.height()); const quint8 pixelSize = BYTE_DEPTH_OF_BUMPMAP * CHANNEL_COUNT_OF_BUMPMAP; const quint32 bytesToFillBumpmapArea = pixelsOfOutputArea * pixelSize; QVector bumpmap(bytesToFillBumpmapArea); quint8 *bumpmapDataPointer = bumpmap.data(); quint32 ki = KoChannelInfo::displayPositionToChannelIndex(m_heightChannel->displayPosition(), device->colorSpace()->channels()); PhongPixelProcessor tileRenderer(pixelsOfInputArea, config); if (progressUpdater) progressUpdater->setProgress(2); //===============RENDER================= QVector toDoubleFuncPtr(device->colorSpace()->channels().count()); KisMathToolbox mathToolbox; if (!mathToolbox.getToDoubleChannelPtr(device->colorSpace()->channels(), toDoubleFuncPtr)) { return; } KisHLineConstIteratorSP iterator; quint32 curPixel = 0; iterator = device->createHLineConstIteratorNG(inputArea.x(), inputArea.y(), inputArea.width() ); if (m_usenormalmap==false) { for (qint32 srcRow = 0; srcRow < inputArea.height(); ++srcRow) { do { const quint8 *data = iterator->oldRawData(); tileRenderer.realheightmap[curPixel] = toDoubleFuncPtr[ki](data, device->colorSpace()->channels()[ki]->pos()); curPixel++; } while (iterator->nextPixel()); iterator->nextRow(); } if (progressUpdater) progressUpdater->setProgress(50); const int tileHeightMinus1 = inputArea.height() - 1; const int tileWidthMinus1 = inputArea.width() - 1; // Foreach INNER pixel in tile for (int y = 1; y < tileHeightMinus1; ++y) { for (int x = 1; x < tileWidthMinus1; ++x) { posup = (y + 1) * inputArea.width() + x; posdown = (y - 1) * inputArea.width() + x; posleft = y * inputArea.width() + x - 1; posright = y * inputArea.width() + x + 1; memcpy(bumpmapDataPointer, tileRenderer.IlluminatePixelFromHeightmap(posup, posdown, posleft, posright).data(), pixelSize); bumpmapDataPointer += pixelSize; } } } else { for (qint32 srcRow = 0; srcRow < inputArea.height(); ++srcRow) { do { const quint8 *data = iterator->oldRawData(); tileRenderer.realheightmap[curPixel] = toDoubleFuncPtr[ki](data, device->colorSpace()->channels()[ki]->pos()); QVector current_pixel_values(4); device->colorSpace()->normalisedChannelsValue(data, current_pixel_values ); //dbgKrita<< "Vector:" << current_pixel_values[2] << "," << current_pixel_values[1] << "," << current_pixel_values[0]; memcpy(bumpmapDataPointer, tileRenderer.IlluminatePixelFromNormalmap(current_pixel_values[2], current_pixel_values[1], current_pixel_values[0]).data(), pixelSize); curPixel++; //pointer that crashes here, but not in the other if statement. bumpmapDataPointer += pixelSize; } while (iterator->nextPixel()); iterator->nextRow(); } } if (progressUpdater) progressUpdater->setProgress(90); KisPaintDeviceSP bumpmapPaintDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb16()); bumpmapPaintDevice->writeBytes(bumpmap.data(), outputArea.x(), outputArea.y(), outputArea.width(), outputArea.height()); - KUndo2Command *leaker = bumpmapPaintDevice->convertTo(device->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + bumpmapPaintDevice->convertTo(device->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KisPainter copier(device); copier.bitBlt(outputArea.x(), outputArea.y(), bumpmapPaintDevice, outputArea.x(), outputArea.y(), outputArea.width(), outputArea.height()); //device->prepareClone(bumpmapPaintDevice); //device->makeCloneFrom(bumpmapPaintDevice, bumpmapPaintDevice->extent()); // THIS COULD BE BUG GY - delete leaker; if (progressUpdater) progressUpdater->setProgress(100); } KisFilterConfigurationSP KisFilterPhongBumpmap::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration(id(), 2); config->setProperty(PHONG_AMBIENT_REFLECTIVITY, 0.2); config->setProperty(PHONG_DIFFUSE_REFLECTIVITY, 0.5); config->setProperty(PHONG_SPECULAR_REFLECTIVITY, 0.3); config->setProperty(PHONG_SHINYNESS_EXPONENT, 2); config->setProperty(USE_NORMALMAP_IS_ENABLED, false); config->setProperty(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED, true); config->setProperty(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED, true); // Indexes are off by 1 simply because arrays start at 0 and the GUI naming scheme started at 1 config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[0], true); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[1], true); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[2], false); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[3], false); config->setProperty(PHONG_ILLUMINANT_COLOR[0], QColor(255, 255, 0)); config->setProperty(PHONG_ILLUMINANT_COLOR[1], QColor(255, 0, 0)); config->setProperty(PHONG_ILLUMINANT_COLOR[2], QColor(0, 0, 255)); config->setProperty(PHONG_ILLUMINANT_COLOR[3], QColor(0, 255, 0)); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[0], 50); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[1], 100); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[2], 150); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[3], 200); config->setProperty(PHONG_ILLUMINANT_INCLINATION[0], 25); config->setProperty(PHONG_ILLUMINANT_INCLINATION[1], 20); config->setProperty(PHONG_ILLUMINANT_INCLINATION[2], 30); config->setProperty(PHONG_ILLUMINANT_INCLINATION[3], 40); return config; } QRect KisFilterPhongBumpmap::neededRect(const QRect &rect, const KisFilterConfigurationSP /*config*/, int /*lod*/) const { return rect.adjusted(-1, -1, 1, 1); } QRect KisFilterPhongBumpmap::changedRect(const QRect &rect, const KisFilterConfigurationSP /*config*/, int /*lod*/) const { return rect; } KisConfigWidget *KisFilterPhongBumpmap::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const { KisPhongBumpmapConfigWidget *w = new KisPhongBumpmapConfigWidget(dev, parent); return w; } diff --git a/plugins/impex/csv/csv_saver.cpp b/plugins/impex/csv/csv_saver.cpp index 40925974ab..e76ee539b8 100644 --- a/plugins/impex/csv/csv_saver.cpp +++ b/plugins/impex/csv/csv_saver.cpp @@ -1,479 +1,478 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "csv_saver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_layer_record.h" CSVSaver::CSVSaver(KisDocument *doc, bool batchMode) : m_image(doc->savingImage()) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVSaver::~CSVSaver() { } KisImageSP CSVSaver::image() { return m_image; } KisImageBuilder_Result CSVSaver::encode(QIODevice *io) { int idx; int start, end; KisNodeSP node; QByteArray ba; KisKeyframeSP keyframe; QVector layers; KisImageAnimationInterface *animation = m_image->animationInterface(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // XXX: Stream was unused? // //DataStream instead of TextStream for correct line endings // QDataStream stream(&f); //Using the original local path QString path = m_doc->localFilePath(); if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); else { // something is wrong: the local file name is not .csv! // trying the given (probably temporary) filename as well KIS_SAFE_ASSERT_RECOVER(0 && "Wrong extension of the saved file!") { path = path.left(path.size() - 4); } } path.append(".frames"); //create directory QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } //according to the QT docs, the slash is a universal directory separator path.append("/"); node = m_image->rootLayer()->firstChild(); //TODO: correct handling of the layer tree. //for now, only top level paint layers are saved idx = 0; while (node) { if (node->inherits("KisLayer")) { KisLayer* paintLayer = qobject_cast(node.data()); CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.prepend(layerRecord); //reverse order! layerRecord->name = paintLayer->name(); layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_"); if (layerRecord->name.isEmpty()) layerRecord->name= QString("Unnamed-%1").arg(idx); layerRecord->visible = (paintLayer->visible()) ? 1 : 0; layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8; layerRecord->blending = convertToBlending(paintLayer->compositeOpId()); layerRecord->layer = paintLayer; layerRecord->channel = paintLayer->original()->keyframeChannel(); layerRecord->last = ""; layerRecord->frame = 0; idx++; } node = node->nextSibling(); } KisTimeRange range = animation->fullClipRange(); start = (range.isValid()) ? range.start() : 0; if (!range.isInfinite()) { end = range.end(); if (end < start) end = start; } else { //undefined length, searching for the last keyframe end = start; for (idx = 0; idx < layers.size(); idx++) { KisRasterKeyframeChannel *channel = layers.at(idx)->channel; if (channel) { keyframe = channel->lastKeyframe(); if ( (!keyframe.isNull()) && (keyframe->time() > end) ) end = keyframe->time(); } } } //create temporary doc for exporting QScopedPointer exportDoc(KisPart::instance()->createDocument()); createTempImage(exportDoc.data()); KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK; if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() //emit m_doc->statusBarMessage(i18n("Saving CSV file...")); //emit m_doc->sigProgress(0); //connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int frame = start; int step = 0; do { qApp->processEvents(); if (m_stop) { retval = KisImageBuilder_RESULT_CANCEL; break; } switch(step) { case 0 : //first row if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) { retval = KisImageBuilder_RESULT_FAILURE; } break; case 1 : //scene header names if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) { retval = KisImageBuilder_RESULT_FAILURE; } break; case 2 : //scene header values ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } //the framerate is an integer here ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } break; case 3 : //layer header values if (io->write("#Layers") < 0) { //Layers retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 4 : if (io->write("\r\n#Density") < 0) { //Density retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 5 : if (io->write("\r\n#Blending") < 0) { //Blending retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 6 : if (io->write("\r\n#Visible") < 0) { //Visible retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) { retval = KisImageBuilder_RESULT_FAILURE; } break; default : //frames if (frame > end) { if (io->write("\r\n") < 0) retval = KisImageBuilder_RESULT_FAILURE; step = 8; break; } ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord *layer = layers.at(idx); KisRasterKeyframeChannel *channel = layer->channel; if (channel) { if (frame == start) { keyframe = channel->activeKeyframeAt(frame); } else { keyframe = channel->keyframeAt(frame); } } else { keyframe.clear(); // without animation } if ( !keyframe.isNull() || (frame == start) ) { if (!m_batchMode) { //emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 / // ((end - start) * layers.size())); } retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx); if (retval != KisImageBuilder_RESULT_OK) break; } ba = QString(", \"%1\"").arg(layer->last).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) retval = KisImageBuilder_RESULT_FAILURE; frame++; step = 6; //keep step here break; } step++; } while((retval == KisImageBuilder_RESULT_OK) && (step < 8)); qDeleteAll(layers); // io->close(); it seems this is not required anymore if (!m_batchMode) { //disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); //emit m_doc->sigProgress(100); //emit m_doc->clearStatusBarMessage(); } QApplication::restoreOverrideCursor(); return retval; } QString CSVSaver::convertToBlending(const QString &opid) { if (opid == COMPOSITE_OVER) return "Color"; if (opid == COMPOSITE_BEHIND) return "Behind"; if (opid == COMPOSITE_ERASE) return "Erase"; // "Shade" if (opid == COMPOSITE_LINEAR_LIGHT) return "Light"; if (opid == COMPOSITE_COLORIZE) return "Colorize"; if (opid == COMPOSITE_HUE) return "Hue"; if ((opid == COMPOSITE_ADD) || (opid == COMPOSITE_LINEAR_DODGE)) return "Add"; if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub"; if (opid == COMPOSITE_MULT) return "Multiply"; if (opid == COMPOSITE_SCREEN) return "Screen"; // "Replace" // "Substitute" if (opid == COMPOSITE_DIFF) return "Difference"; if (opid == COMPOSITE_DIVIDE) return "Divide"; if (opid == COMPOSITE_OVERLAY) return "Overlay"; if (opid == COMPOSITE_DODGE) return "Light2"; if (opid == COMPOSITE_BURN) return "Shade2"; if (opid == COMPOSITE_HARD_LIGHT) return "HardLight"; if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) || (opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight"; if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract"; if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge"; if (opid == COMPOSITE_SUBTRACT) return "Sub2"; if (opid == COMPOSITE_DARKEN) return "Darken"; if (opid == COMPOSITE_LIGHTEN) return "Lighten"; if (opid == COMPOSITE_SATURATION) return "Saturation"; return "Color"; } KisImageBuilder_Result CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx) { //render to the temp layer KisImageSP image = exportDoc->savingImage(); if (!image) image= exportDoc->image(); KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection(); if (!keyframe.isNull()) { layer->channel->fetchFrame(keyframe, device); } else { device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation } QRect bounds = device->exactBounds(); if (bounds.isEmpty()) { layer->last = ""; //empty frame return KisImageBuilder_RESULT_OK; } layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0')); QString filename = path; filename.append(layer->last); //save to PNG KisSequentialConstIterator it(device, image->bounds()); const KoColorSpace* cs = device->colorSpace(); bool isThereAlpha = false; while (it.nextPixel()) { if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) { isThereAlpha = true; break; } } if (!KisPNGConverter::isColorSpaceSupported(cs)) { device = new KisPaintDevice(*device.data()); - KUndo2Command *cmd= device->convertTo(KoColorSpaceRegistry::instance()->rgb8()); - delete cmd; + device->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.alpha = isThereAlpha; options.interlace = false; options.compression = 8; options.tryToSaveAsIndexed = false; options.transparencyFillColor = QColor(0,0,0); options.saveSRGBProfile = true; //TVPaint can use only sRGB options.forceSRGB = false; KisPNGConverter kpc(exportDoc); KisImageBuilder_Result result = kpc.buildFile(filename, image->bounds(), image->xRes(), image->yRes(), device, image->beginAnnotations(), image->endAnnotations(), options, (KisMetaData::Store* )0 ); return result; } void CSVSaver::createTempImage(KisDocument* exportDoc) { exportDoc->setInfiniteAutoSaveInterval(); exportDoc->setFileBatchMode(true); KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(), m_image->width(), m_image->height(), m_image->colorSpace(), QString()); exportImage->setResolution(m_image->xRes(), m_image->yRes()); exportDoc->setCurrentImage(exportImage); KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8); exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0)); } KisImageBuilder_Result CSVSaver::buildAnimation(QIODevice *io) { if (!m_image) { return KisImageBuilder_RESULT_EMPTY; } return encode(io); } void CSVSaver::cancel() { m_stop = true; } diff --git a/plugins/impex/heightmap/kis_heightmap_export.cpp b/plugins/impex/heightmap/kis_heightmap_export.cpp index 3bab487f39..085dc7e4f3 100644 --- a/plugins/impex/heightmap/kis_heightmap_export.cpp +++ b/plugins/impex/heightmap/kis_heightmap_export.cpp @@ -1,149 +1,148 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" #include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(KisHeightMapExportFactory, "krita_heightmap_export.json", registerPlugin();) template static void writeData(KisPaintDeviceSP pd, const QRect &bounds, QDataStream &out_stream) { KIS_ASSERT_RECOVER_RETURN(pd); KisSequentialConstIterator it(pd, bounds); while (it.nextPixel()) { out_stream << KoGrayTraits::gray(const_cast(it.rawDataConst())); } } KisHeightMapExport::KisHeightMapExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapExport::~KisHeightMapExport() { } KisPropertiesConfigurationSP KisHeightMapExport::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("endianness", 0); return cfg; } KisConfigWidget *KisHeightMapExport::createConfigurationWidget(QWidget *parent, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); bool export_mode = true; KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(parent, export_mode); return wdg; } void KisHeightMapExport::initializeCapabilities() { if (mimeType() == "image/x-r8") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R8 Heightmap"); } else if (mimeType() == "image/x-r16") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R16 Heightmap"); } else if (mimeType() == "image/x-r32") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Float32BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R32 Heightmap"); } } KisImportExportFilter::ConversionStatus KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", KisImportExportFilter::WrongFormat); KisImageSP image = document->savingImage(); QDataStream::ByteOrder bo = configuration->getInt("endianness", 1) == 0 ? QDataStream::BigEndian : QDataStream::LittleEndian; KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); QDataStream s(io); s.setByteOrder(bo); // needed for 32bit float data s.setFloatingPointPrecision(QDataStream::SinglePrecision); KoID target_co_model = GrayAColorModelID; KoID target_co_depth = KisHeightmapUtils::mimeTypeToKoID(mimeType()); KIS_ASSERT(!target_co_depth.id().isNull()); if (pd->colorSpace()->colorModelId() != target_co_model || pd->colorSpace()->colorDepthId() != target_co_depth) { pd = new KisPaintDevice(*pd.data()); - KUndo2Command *cmd = pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_co_model.id(), target_co_depth.id())); - delete cmd; + pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_co_model.id(), target_co_depth.id())); } if (target_co_depth == Float32BitsColorDepthID) { writeData(pd, image->bounds(), s); } else if (target_co_depth == Integer16BitsColorDepthID) { writeData(pd, image->bounds(), s); } else if (target_co_depth == Integer8BitsColorDepthID) { writeData(pd, image->bounds(), s); } else { return KisImportExportFilter::InternalError; } return KisImportExportFilter::OK; } #include "kis_heightmap_export.moc" diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index b2c7484694..eca195fde1 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,732 +1,730 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { void jpegErrorExit ( j_common_ptr cinfo ) { char jpegLastErrorMsg[JMSG_LENGTH_MAX]; /* Create the message */ ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); /* Jump to the setjmp point */ throw std::runtime_error( jpegLastErrorMsg ); // or your preferred exception ... } J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } struct KisJPEGConverter::Private { Private(KisDocument *doc, bool batchMode) : doc(doc), stop(false), batchMode(batchMode) {} KisImageSP image; KisDocument *doc; bool stop; bool batchMode; }; KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_d(new Private(doc, batchMode)) { } KisJPEGConverter::~KisJPEGConverter() { } KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_decompress(&cinfo); KisJPEGSource::setSource(&cinfo, io); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } uchar* profile_data; uint profile_len; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id()); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageSP if (!m_d->image) { m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_d->image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); QBuffer buf(&byteArray); iptcIO->loadFrom(layer->metaData(), &buf); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; return KisImageBuilder_RESULT_OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_FAILURE; } } KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageSP image = KisImageSP(layer->image()); if (!image) return KisImageBuilder_RESULT_EMPTY; const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (color_type == JCS_UNKNOWN) { - KUndo2Command *tmp = layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - delete tmp; + layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); - KUndo2Command *tmp = layer->paintDevice()->convertTo(dst); - delete tmp; + layer->paintDevice()->convertTo(dst); cs = dst; color_type = JCS_RGB; } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, io); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_OK; } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/psd/psd_layer_record.cpp b/plugins/impex/psd/psd_layer_record.cpp index 0cee72b08a..dabfd4eeca 100644 --- a/plugins/impex/psd/psd_layer_record.cpp +++ b/plugins/impex/psd/psd_layer_record.cpp @@ -1,762 +1,762 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_layer_record.h" #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include "psd_utils.h" #include "psd_header.h" #include "compression.h" #include #include #include #include #include #include #include #include "psd_pixel_utils.h" #include // Just for pretty debug messages QString channelIdToChannelType(int channelId, psd_color_mode colormode) { switch(channelId) { case -3: return "Real User Supplied Layer Mask (when both a user mask and a vector mask are present"; case -2: return "User Supplied Layer Mask"; case -1: return "Transparency mask"; case 0: switch(colormode) { case Bitmap: case Indexed: return QString("bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return "gray"; case RGB: case RGB48: return "red"; case Lab: case Lab48: return "L"; case CMYK: case CMYK64: return "cyan"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 1: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return "green"; case Lab: case Lab48: return "a"; case CMYK: case CMYK64: return "Magenta"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 2: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return "blue"; case Lab: case Lab48: return "b"; case CMYK: case CMYK64: return "yellow"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 3: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return QString("alpha: %1").arg(channelId); case Lab: case Lab48: return QString("alpha: %1").arg(channelId); case CMYK: case CMYK64: return "Key"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; default: return QString("unknown: %1").arg(channelId); }; } PSDLayerRecord::PSDLayerRecord(const PSDHeader& header) : top(0) , left(0) , bottom(0) , right(0) , nChannels(0) , opacity(0) , clipping(0) , transparencyProtected(false) , visible(true) , irrelevant(false) , layerName("UNINITIALIZED") , infoBlocks(header) , m_transparencyMaskSizeOffset(0) , m_header(header) { } bool PSDLayerRecord::read(QIODevice* io) { dbgFile << "Going to read layer record. Pos:" << io->pos(); if (!psdread(io, &top) || !psdread(io, &left) || !psdread(io, &bottom) || !psdread(io, &right) || !psdread(io, &nChannels)) { error = "could not read layer record"; return false; } dbgFile << "\ttop" << top << "left" << left << "bottom" << bottom << "right" << right << "number of channels" << nChannels; Q_ASSERT(top <= bottom); Q_ASSERT(left <= right); Q_ASSERT(nChannels > 0); if (nChannels < 1) { error = QString("Not enough channels. Got: %1").arg(nChannels); return false; } if (nChannels > MAX_CHANNELS) { error = QString("Too many channels. Got: %1").arg(nChannels); return false; } for (int i = 0; i < nChannels; ++i) { if (io->atEnd()) { error = "Could not read enough data for channels"; return false; } ChannelInfo* info = new ChannelInfo; if (!psdread(io, &info->channelId)) { error = "could not read channel id"; delete info; return false; } bool r; if (m_header.version == 1) { quint32 channelDataLength; r = psdread(io, &channelDataLength); info->channelDataLength = (quint64)channelDataLength; } else { r = psdread(io, &info->channelDataLength); } if (!r) { error = "Could not read length for channel data"; delete info; return false; } dbgFile << "\tchannel" << i << "id" << channelIdToChannelType(info->channelId, m_header.colormode) << "length" << info->channelDataLength << "start" << info->channelDataStart << "offset" << info->channelOffset << "channelInfoPosition" << info->channelInfoPosition; channelInfoRecords << info; } if (!psd_read_blendmode(io, blendModeKey)) { error = QString("Could not read blend mode key. Got: %1").arg(blendModeKey); return false; } dbgFile << "\tBlend mode" << blendModeKey << "pos" << io->pos(); if (!psdread(io, &opacity)) { error = "Could not read opacity"; return false; } dbgFile << "\tOpacity" << opacity << io->pos(); if (!psdread(io, &clipping)) { error = "Could not read clipping"; return false; } dbgFile << "\tclipping" << clipping << io->pos(); quint8 flags; if (!psdread(io, &flags)) { error = "Could not read flags"; return false; } dbgFile << "\tflags" << flags << io->pos(); transparencyProtected = flags & 1 ? true : false; dbgFile << "\ttransparency protected" << transparencyProtected; visible = flags & 2 ? false : true; dbgFile << "\tvisible" << visible; if (flags & 8) { irrelevant = flags & 16 ? true : false; } else { irrelevant = false; } dbgFile << "\tirrelevant" << irrelevant; dbgFile << "\tfiller at " << io->pos(); quint8 filler; if (!psdread(io, &filler) || filler != 0) { error = "Could not read padding"; return false; } dbgFile << "\tGoing to read extra data length" << io->pos(); quint32 extraDataLength; if (!psdread(io, &extraDataLength) || io->bytesAvailable() < extraDataLength) { error = QString("Could not read extra layer data: %1 at pos %2").arg(extraDataLength).arg(io->pos()); return false; } dbgFile << "\tExtra data length" << extraDataLength; if (extraDataLength > 0) { dbgFile << "Going to read extra data field. Bytes available: " << io->bytesAvailable() << "pos" << io->pos(); // See https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_22582 quint32 layerMaskLength = 1; // invalid... if (!psdread(io, &layerMaskLength) || io->bytesAvailable() < layerMaskLength || !(layerMaskLength == 0 || layerMaskLength == 20 || layerMaskLength == 36)) { error = QString("Could not read layer mask length: %1").arg(layerMaskLength); return false; } memset(&layerMask, 0, sizeof(LayerMaskData)); if (layerMaskLength == 20 || layerMaskLength == 36) { if (!psdread(io, &layerMask.top) || !psdread(io, &layerMask.left) || !psdread(io, &layerMask.bottom) || !psdread(io, &layerMask.right) || !psdread(io, &layerMask.defaultColor) || !psdread(io, &flags)) { error = "could not read mask record"; return false; } } if (layerMaskLength == 20) { quint16 padding; if (!psdread(io, &padding)) { error = "Could not read layer mask padding"; return false; } } // If it's 36, that is, bit four of the flags is set, we also need to read the 'real' flags, background and rectangle if (layerMaskLength == 36 ) { if (!psdread(io, &flags) || !psdread(io, &layerMask.defaultColor) || !psdread(io, &layerMask.top) || !psdread(io, &layerMask.left) || !psdread(io, &layerMask.bottom) || !psdread(io, &layerMask.top)) { error = "could not read 'real' mask record"; return false; } } layerMask.positionedRelativeToLayer = flags & 1 ? true : false; layerMask.disabled = flags & 2 ? true : false; layerMask.invertLayerMaskWhenBlending = flags & 4 ? true : false; dbgFile << "\tRead layer mask/adjustment layer data. Length of block:" << layerMaskLength << "pos" << io->pos(); // layer blending thingies quint32 blendingDataLength; if (!psdread(io, &blendingDataLength) || io->bytesAvailable() < blendingDataLength) { error = "Could not read extra blending data."; return false; } //dbgFile << "blending block data length" << blendingDataLength << ", pos" << io->pos(); blendingRanges.data = io->read(blendingDataLength); if ((quint32)blendingRanges.data.size() != blendingDataLength) { error = QString("Got %1 bytes for the blending range block, needed %2").arg(blendingRanges.data.size(), blendingDataLength); } /* // XXX: reading this block correctly failed, I have more channel ranges than I'd expected. if (!psdread(io, &blendingRanges.blackValues[0]) || !psdread(io, &blendingRanges.blackValues[1]) || !psdread(io, &blendingRanges.whiteValues[0]) || !psdread(io, &blendingRanges.whiteValues[1]) || !psdread(io, &blendingRanges.compositeGrayBlendDestinationRange)) { error = "Could not read blending black/white values"; return false; } for (int i = 0; i < nChannels; ++i) { quint32 src; quint32 dst; if (!psdread(io, &src) || !psdread(io, &dst)) { error = QString("could not read src/dst range for channel %1").arg(i); return false; } dbgFile << "\tread range " << src << "to" << dst << "for channel" << i; blendingRanges.sourceDestinationRanges << QPair(src, dst); } */ dbgFile << "\tGoing to read layer name at" << io->pos(); quint8 layerNameLength; if (!psdread(io, &layerNameLength)) { error = "Could not read layer name length"; return false; } dbgFile << "\tlayer name length unpadded" << layerNameLength << "pos" << io->pos(); layerNameLength = ((layerNameLength + 1 + 3) & ~0x03) - 1; dbgFile << "\tlayer name length padded" << layerNameLength << "pos" << io->pos(); layerName = io->read(layerNameLength); dbgFile << "\tlayer name" << layerName << io->pos(); if (!infoBlocks.read(io)) { error = infoBlocks.error; return false; } if (infoBlocks.keys.contains("luni") && !infoBlocks.unicodeLayerName.isEmpty()) { layerName = infoBlocks.unicodeLayerName; } } return valid(); } void PSDLayerRecord::write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType, const QDomDocument &stylesXmlDoc, bool useLfxsLayerStyleFormat) { dbgFile << "writing layer info record" << "at" << io->pos(); m_layerContentDevice = layerContentDevice; m_onlyTransparencyMask = onlyTransparencyMask; m_onlyTransparencyMaskRect = maskRect; dbgFile << "saving layer record for " << layerName << "at pos" << io->pos(); dbgFile << "\ttop" << top << "left" << left << "bottom" << bottom << "right" << right << "number of channels" << nChannels; Q_ASSERT(left <= right); Q_ASSERT(top <= bottom); Q_ASSERT(nChannels > 0); try { const QRect layerRect(left, top, right - left, bottom - top); KisAslWriterUtils::writeRect(layerRect, io); { quint16 realNumberOfChannels = nChannels + bool(m_onlyTransparencyMask); SAFE_WRITE_EX(io, realNumberOfChannels); } Q_FOREACH (ChannelInfo *channel, channelInfoRecords) { SAFE_WRITE_EX(io, (quint16)channel->channelId); channel->channelInfoPosition = io->pos(); // to be filled in when we know how big channel block is const quint32 fakeChannelSize = 0; SAFE_WRITE_EX(io, fakeChannelSize); } if (m_onlyTransparencyMask) { const quint16 userSuppliedMaskChannelId = -2; SAFE_WRITE_EX(io, userSuppliedMaskChannelId); m_transparencyMaskSizeOffset = io->pos(); const quint32 fakeTransparencyMaskSize = 0; SAFE_WRITE_EX(io, fakeTransparencyMaskSize); } // blend mode dbgFile << ppVar(blendModeKey) << ppVar(io->pos()); KisAslWriterUtils::writeFixedString("8BIM", io); KisAslWriterUtils::writeFixedString(blendModeKey, io); SAFE_WRITE_EX(io, opacity); SAFE_WRITE_EX(io, clipping); // unused // visibility and protection quint8 flags = 0; if (transparencyProtected) flags |= 1; if (!visible) flags |= 2; if (irrelevant) { flags |= (1 << 3) | (1 << 4); } SAFE_WRITE_EX(io, flags); { quint8 padding = 0; SAFE_WRITE_EX(io, padding); } { // extra fields with their own length tag KisAslWriterUtils::OffsetStreamPusher extraDataSizeTag(io); if (m_onlyTransparencyMask) { { const quint32 layerMaskDataSize = 20; // support simple case only SAFE_WRITE_EX(io, layerMaskDataSize); } KisAslWriterUtils::writeRect(m_onlyTransparencyMaskRect, io); { // NOTE: in PSD the default color of the mask is stored in 1 byte value! // Even when the mask is actually 16/32 bit! I have no idea how it is // actually treated in this case. KIS_ASSERT_RECOVER_NOOP(m_onlyTransparencyMask->paintDevice()->pixelSize() == 1); const quint8 defaultPixel = *m_onlyTransparencyMask->paintDevice()->defaultPixel().data(); SAFE_WRITE_EX(io, defaultPixel); } { const quint8 maskFlags = 0; // nothing serious SAFE_WRITE_EX(io, maskFlags); const quint16 padding = 0; // 2-byte padding SAFE_WRITE_EX(io, padding); } } else { const quint32 nullLayerMaskDataSize = 0; SAFE_WRITE_EX(io, nullLayerMaskDataSize); } { // blending ranges are not implemented yet const quint32 nullBlendingRangesSize = 0; SAFE_WRITE_EX(io, nullBlendingRangesSize); } // layer name: Pascal string, padded to a multiple of 4 bytes. psdwrite_pascalstring(io, layerName, 4); PsdAdditionalLayerInfoBlock additionalInfoBlock(m_header); // write 'luni' data block additionalInfoBlock.writeLuniBlockEx(io, layerName); // write 'lsct' data block if (sectionType != psd_other) { additionalInfoBlock.writeLsctBlockEx(io, sectionType, isPassThrough, blendModeKey); } // write 'lfx2' data block if (!stylesXmlDoc.isNull()) { additionalInfoBlock.writeLfx2BlockEx(io, stylesXmlDoc, useLfxsLayerStyleFormat); } } } catch (KisAslWriterUtils::ASLWriteException &e) { throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } } KisPaintDeviceSP PSDLayerRecord::convertMaskDeviceIfNeeded(KisPaintDeviceSP dev) { KisPaintDeviceSP result = dev; if (m_header.channelDepth == 16) { result = new KisPaintDevice(*dev); - delete result->convertTo(KoColorSpaceRegistry::instance()->alpha16()); + result->convertTo(KoColorSpaceRegistry::instance()->alpha16()); } else if (m_header.channelDepth == 32) { result = new KisPaintDevice(*dev); - delete result->convertTo(KoColorSpaceRegistry::instance()->alpha32f()); + result->convertTo(KoColorSpaceRegistry::instance()->alpha32f()); } return result; } void PSDLayerRecord::writeTransparencyMaskPixelData(QIODevice *io) { if (m_onlyTransparencyMask) { KisPaintDeviceSP device = convertMaskDeviceIfNeeded(m_onlyTransparencyMask->paintDevice()); QByteArray buffer(device->pixelSize() * m_onlyTransparencyMaskRect.width() * m_onlyTransparencyMaskRect.height(), 0); device->readBytes((quint8*)buffer.data(), m_onlyTransparencyMaskRect); PsdPixelUtils::writeChannelDataRLE(io, (quint8*)buffer.data(), device->pixelSize(), m_onlyTransparencyMaskRect, m_transparencyMaskSizeOffset, -1, true); } } void PSDLayerRecord::writePixelData(QIODevice *io) { try { writePixelDataImpl(io); } catch (KisAslWriterUtils::ASLWriteException &e) { throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } } void PSDLayerRecord::writePixelDataImpl(QIODevice *io) { dbgFile << "writing pixel data for layer" << layerName << "at" << io->pos(); KisPaintDeviceSP dev = m_layerContentDevice; const QRect rc(left, top, right - left, bottom - top); if (rc.isEmpty()) { dbgFile << "Layer is empty! Writing placeholder information."; for (int i = 0; i < nChannels; i++) { const ChannelInfo *channelInfo = channelInfoRecords[i]; KisAslWriterUtils::OffsetStreamPusher channelBlockSizeExternalTag(io, 0, channelInfo->channelInfoPosition); SAFE_WRITE_EX(io, (quint16)Compression::Uncompressed); } writeTransparencyMaskPixelData(io); return; } // now write all the channels in display order dbgFile << "layer" << layerName; const int channelSize = m_header.channelDepth / 8; const psd_color_mode colorMode = m_header.colormode; QVector writingInfoList; Q_FOREACH (const ChannelInfo *channelInfo, channelInfoRecords) { writingInfoList << PsdPixelUtils::ChannelWritingInfo(channelInfo->channelId, channelInfo->channelInfoPosition); } PsdPixelUtils::writePixelDataCommon(io, dev, rc, colorMode, channelSize, true, true, writingInfoList); writeTransparencyMaskPixelData(io); } bool PSDLayerRecord::valid() { // XXX: check validity! return true; } bool PSDLayerRecord::readPixelData(QIODevice *io, KisPaintDeviceSP device) { dbgFile << "Reading pixel data for layer" << layerName << "pos" << io->pos(); const int channelSize = m_header.channelDepth / 8; const QRect layerRect = QRect(left, top, right - left, bottom - top); try { PsdPixelUtils::readChannels(io, device, m_header.colormode, channelSize, layerRect, channelInfoRecords); } catch (KisAslReaderUtils::ASLParseException &e) { device->clear(); error = e.what(); return false; } return true; } QRect PSDLayerRecord::channelRect(ChannelInfo *channel) const { QRect result; if (channel->channelId < -1) { result = QRect(layerMask.left, layerMask.top, layerMask.right - layerMask.left, layerMask.bottom - layerMask.top); } else { result = QRect(left, top, right - left, bottom - top); } return result; } bool PSDLayerRecord::readMask(QIODevice *io, KisPaintDeviceSP dev, ChannelInfo *channelInfo) { KIS_ASSERT_RECOVER(channelInfo->channelId < -1) { return false; } dbgFile << "Going to read" << channelIdToChannelType(channelInfo->channelId, m_header.colormode) << "mask"; QRect maskRect = channelRect(channelInfo); if (maskRect.isEmpty()) { dbgFile << "Empty Channel"; return true; } // the device must be a pixel selection KIS_ASSERT_RECOVER(dev->pixelSize() == 1) { return false; } dev->setDefaultPixel(KoColor(&layerMask.defaultColor, dev->colorSpace())); const int pixelSize = m_header.channelDepth == 16 ? 2 : m_header.channelDepth == 32 ? 4 : 1; QVector infoRecords; infoRecords << channelInfo; PsdPixelUtils::readAlphaMaskChannels(io, dev, pixelSize, maskRect, infoRecords); return true; } QDebug operator<<(QDebug dbg, const PSDLayerRecord &layer) { #ifndef NODEBUG dbg.nospace() << "valid: " << const_cast(&layer)->valid(); dbg.nospace() << ", name: " << layer.layerName; dbg.nospace() << ", top: " << layer.top; dbg.nospace() << ", left:" << layer.left; dbg.nospace() << ", bottom: " << layer.bottom; dbg.nospace() << ", right: " << layer.right; dbg.nospace() << ", number of channels: " << layer.nChannels; dbg.nospace() << ", blendModeKey: " << layer.blendModeKey; dbg.nospace() << ", opacity: " << layer.opacity; dbg.nospace() << ", clipping: " << layer.clipping; dbg.nospace() << ", transparency protected: " << layer.transparencyProtected; dbg.nospace() << ", visible: " << layer.visible; dbg.nospace() << ", irrelevant: " << layer.irrelevant << "\n"; Q_FOREACH (const ChannelInfo* channel, layer.channelInfoRecords) { dbg.space() << channel; } #endif return dbg.nospace(); } QDebug operator<<(QDebug dbg, const ChannelInfo &channel) { #ifndef NODEBUG dbg.nospace() << "\tChannel type" << channel.channelId << "size: " << channel.channelDataLength << "compression type" << channel.compressionType << "\n"; #endif return dbg.nospace(); } diff --git a/plugins/impex/spriter/kis_spriter_export.cpp b/plugins/impex/spriter/kis_spriter_export.cpp index a4684f7d70..a55c47948a 100644 --- a/plugins/impex/spriter/kis_spriter_export.cpp +++ b/plugins/impex/spriter/kis_spriter_export.cpp @@ -1,618 +1,617 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_spriter_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for KisDegreesToRadians #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisSpriterExportFactory, "krita_spriter_export.json", registerPlugin();) KisSpriterExport::KisSpriterExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSpriterExport::~KisSpriterExport() { } bool KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName) { QFileInfo fi(fileName); QDir d = fi.absoluteDir(); d.mkpath(d.path()); QRect rc = m_image->bounds().intersected(dev->exactBounds()); if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { dev = new KisPaintDevice(*dev.data()); - KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); - delete cmd; + dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.forceSRGB = true; vKisAnnotationSP_it beginIt = m_image->beginAnnotations(); vKisAnnotationSP_it endIt = m_image->endAnnotations(); KisPNGConverter converter(0); KisImageBuilder_Result res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0); return (res == KisImageBuilder_RESULT_OK); } void KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId) { // qDebug() << "parseFolder: parent" << parentGroup->name() // << "folderName" << folderName // << "basepath" << basePath; int currentFolder=0; if(folderId == 0) { folderId = ¤tFolder; } QString pathName; if (!folderName.isEmpty()) { pathName = folderName + "/"; } KisNodeSP child = parentGroup->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { parseFolder(qobject_cast(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId); } child = child->prevSibling(); } Folder folder; folder.id = *folderId; folder.name = folderName; folder.groupName = parentGroup->name(); int fileId = 0; child = parentGroup->lastChild(); while (child) { if (child->visible() && !child->inherits("KisGroupLayer") && !child->inherits("KisMask")) { QRectF rc = m_image->bounds().intersected(child->exactBounds()); QString layerBaseName = child->name().split(" ").first(); SpriterFile file; file.id = fileId++; file.pathName = pathName; file.baseName = layerBaseName; file.layerName = child->name(); file.name = folderName + "/" + layerBaseName + ".png"; qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); file.width = xmax - xmin; file.height = ymax - ymin; file.x = xmin; file.y = ymin; //qDebug() << "Created file" << file.id << file.name << file.pathName << file.baseName << file.width << file.height << file.layerName; savePaintDevice(child->projection(), basePath + file.name); folder.files.append(file); } child = child->prevSibling(); } if (folder.files.size() > 0) { //qDebug() << "Adding folder" << folder.id << folder.name << folder.groupName << folder.files.length(); m_folders.append(folder); (*folderId)++; } } Bone *KisSpriterExport::parseBone(const Bone *parent, KisGroupLayerSP groupLayer) { static int boneId = 0; QString groupBaseName = groupLayer->name().split(" ").first(); Bone *bone = new Bone; bone->id = boneId++; bone->parentBone = parent; bone->name = groupBaseName; if (m_boneLayer) { QRectF rc = m_image->bounds().intersected(m_boneLayer->exactBounds()); qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); bone->x = (xmin + xmax) / 2; bone->y = -(ymin + ymax) / 2; bone->width = xmax - xmin; bone->height = ymax - ymin; } else { bone->x = 0.0; bone->y = 0.0; bone->width = 0.0; bone->height = 0.0; } if (parent) { bone->localX = bone->x - parent->x; bone->localY = bone->y - parent->y; } else { bone->localX = bone->x; bone->localY = bone->y; } bone->localAngle = 0.0; bone->localScaleX = 1.0; bone->localScaleY = 1.0; KisNodeSP child = groupLayer->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { bone->bones.append(parseBone(bone, qobject_cast(child.data()))); } child = child->prevSibling(); } //qDebug() << "Created bone" << bone->id << "with" << bone->bones.size() << "bones"; return bone; } void copyBone(Bone *startBone) { startBone->fixLocalX = startBone->localX; startBone->fixLocalY = startBone->localY; startBone->fixLocalAngle = startBone->localAngle; startBone->fixLocalScaleX= startBone->localScaleX; startBone->fixLocalScaleY= startBone->localScaleY; Q_FOREACH(Bone *child, startBone->bones) { copyBone(child); } } void KisSpriterExport::fixBone(Bone *bone) { qreal boneLocalAngle = 0; qreal boneLocalScaleX = 1; if (bone->bones.length() >= 1) { // if a bone has one or more children, point at first child Bone *childBone = bone->bones[0]; qreal dx = childBone->x - bone->x; qreal dy = childBone->y - bone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } else if (bone->parentBone) { // else, if bone has parent, point away from parent qreal dx = bone->x - bone->parentBone->x; qreal dy = bone->y - bone->parentBone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } // adjust bone angle bone->fixLocalAngle += boneLocalAngle; bone->fixLocalScaleX *= boneLocalScaleX; // rotate all the child bones back to world position for (int i = 0; i < bone->bones.length(); ++i) { Bone *childBone = bone->bones[i]; qreal tx = childBone->fixLocalX; qreal ty = childBone->fixLocalY; childBone->fixLocalX = tx * cos(-boneLocalAngle) - ty * sin(-boneLocalAngle); childBone->fixLocalY = tx * sin(-boneLocalAngle) + ty * cos(-boneLocalAngle); childBone->fixLocalX /= boneLocalScaleX; childBone->fixLocalAngle -= boneLocalAngle; childBone->fixLocalScaleX /= boneLocalScaleX; } // rotate all the child objects back to world position for (int i = 0; i < m_objects.length(); ++i) { if (m_objects[i].bone == bone) { m_objects[i].fixLocalAngle -= boneLocalAngle; m_objects[i].fixLocalScaleX /= boneLocalScaleX; } } // process all child bones for (int i = 0; i < bone->bones.length(); ++i) { fixBone(bone->bones[i]); } } void KisSpriterExport::writeBoneRef(const Bone *bone, QDomElement &key, QDomDocument &scml) { if (!bone) return; QDomElement boneRef = scml.createElement("bone_ref"); key.appendChild(boneRef); boneRef.setAttribute("id", bone->id); if (bone->parentBone) { boneRef.setAttribute("parent", bone->parentBone->id); } boneRef.setAttribute("timeline", m_timelineid++); boneRef.setAttribute("key", "0"); Q_FOREACH(const Bone *childBone, bone->bones) { writeBoneRef(childBone, key, scml); } } void KisSpriterExport::writeBone(const Bone *bone, QDomElement &animation, QDomDocument &scml) { if (!bone) return; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid); timeline.setAttribute("name", bone->name); timeline.setAttribute("object_type", "bone"); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", 0); QDomElement boneEl = scml.createElement("bone"); key.appendChild(boneEl); boneEl.setAttribute("x", QString::number(bone->fixLocalX, 'f', 2)); boneEl.setAttribute("y", QString::number(bone->fixLocalY, 'f', 2)); boneEl.setAttribute("angle", QString::number(bone->fixLocalAngle, 'f', 2)); boneEl.setAttribute("scale_x", QString::number(bone->fixLocalScaleX, 'f', 2)); boneEl.setAttribute("scale_y", QString::number(bone->fixLocalScaleY, 'f', 2)); m_timelineid++; Q_FOREACH(const Bone *childBone, bone->bones) { writeBone(childBone, animation, scml); } } void KisSpriterExport::fillScml(QDomDocument &scml, const QString &entityName) { //qDebug() << "Creating scml" << entityName; QDomElement root = scml.createElement("spriter_data"); scml.appendChild(root); root.setAttribute("scml_version", 1); root.setAttribute("generator", "krita"); root.setAttribute("generator_version", qApp->applicationVersion()); Q_FOREACH(const Folder &folder, m_folders) { QDomElement fe = scml.createElement("folder"); root.appendChild(fe); fe.setAttribute("id", folder.id); fe.setAttribute("name", folder.name); Q_FOREACH(const SpriterFile &file, folder.files) { QDomElement fileElement = scml.createElement("file"); fe.appendChild(fileElement); fileElement.setAttribute("id", file.id); fileElement.setAttribute("name", file.name); fileElement.setAttribute("width", QString::number(file.width, 'f', 2)); fileElement.setAttribute("height", QString::number(file.height, 'f', 2)); // qreal pivotX=0; // qreal pivotY=1; // Q_FOREACH(const SpriterObject &object, m_objects) { // if(file.id == object.fileId) // { // pivotX = (0.0 -(object.fixLocalX / file.width)); // pivotY = (1.0 -(object.fixLocalY / file.height)); // break; // } // } // fileElement.setAttribute("pivot_x", QString::number(pivotX, 'f', 2)); // fileElement.setAttribute("pivot_y", QString::number(pivotY, 'f', 2)); } } // entity QDomElement entity = scml.createElement("entity"); root.appendChild(entity); entity.setAttribute("id", "0"); entity.setAttribute("name", entityName); // entity/animation QDomElement animation = scml.createElement("animation"); entity.appendChild(animation); animation.setAttribute("id", "0"); animation.setAttribute("name", "default"); animation.setAttribute("length", "1000"); animation.setAttribute("looping", "false"); // entity/animation/mainline QDomElement mainline = scml.createElement("mainline"); animation.appendChild(mainline); QDomElement key = scml.createElement("key"); mainline.appendChild(key); key.setAttribute("id", "0"); m_timelineid = 0; writeBoneRef(m_rootBone, key, scml); Q_FOREACH(const SpriterObject &object, m_objects) { QDomElement oe = scml.createElement("object_ref"); key.appendChild(oe); oe.setAttribute("id", object.id); if (object.bone) { oe.setAttribute("parent", object.bone->id); } oe.setAttribute("timeline", m_timelineid++); oe.setAttribute("key", "0"); oe.setAttribute("z_index", object.id); } // entity/animation/timeline m_timelineid = 0; if (m_rootBone) { writeBone(m_rootBone, animation, scml); } Q_FOREACH(const SpriterObject &object, m_objects) { Folder folder; Q_FOREACH(const Folder & f, m_folders) { if (f.id == object.folderId) { folder = f; break; } } SpriterFile file; file.id = -1; Q_FOREACH(const SpriterFile &f, folder.files) { if (f.id == object.fileId) { file = f; break; } } Q_ASSERT(file.id >= 0); QString objectName = "object-" + file.baseName; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid++); timeline.setAttribute("name", objectName); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", "0"); QDomElement objectEl = scml.createElement("object"); key.appendChild(objectEl); objectEl.setAttribute("folder", object.folderId); objectEl.setAttribute("file", object.fileId); objectEl.setAttribute("x", object.fixLocalX); objectEl.setAttribute("y", object.fixLocalY); objectEl.setAttribute("angle", QString::number(kisRadiansToDegrees(object.fixLocalAngle), 'f', 2)); objectEl.setAttribute("scale_x", QString::number(object.fixLocalScaleX, 'f', 2)); objectEl.setAttribute("scale_y", QString::number(object.fixLocalScaleY, 'f', 2)); } } Bone *findBoneByName(Bone *startBone, const QString &name) { if (!startBone) return 0; //qDebug() << "findBoneByName" << name << "starting with" << startBone->name; if (startBone->name == name) { return startBone; } Q_FOREACH(Bone *child, startBone->bones) { //qDebug() << "looking for" << name << "found" << child->name; if (child->name == name) { return child; } Bone *grandChild = findBoneByName(child, name); if (grandChild){ return grandChild; } } return 0; } KisImportExportFilter::ConversionStatus KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QFileInfo fi(filename()); m_image = document->savingImage(); if (m_image->rootLayer()->childCount() == 0) { return KisImportExportFilter::UsageError; } KisGroupLayerSP root = m_image->rootLayer(); m_boneLayer = qobject_cast(root->findChildByName("bone").data()); //qDebug() << "Found boneLayer" << m_boneLayer; m_rootLayer= qobject_cast(root->findChildByName("root").data()); //qDebug() << "Fond rootLayer" << m_rootLayer; parseFolder(m_image->rootLayer(), "", fi.absolutePath()); m_rootBone = 0; if (m_rootLayer) { m_rootBone = parseBone(0, m_rootLayer); } // Generate objects int objectId = 0; for (int folderIndex = 0, folderCount = m_folders.size(); folderIndex < folderCount; ++folderIndex) { Folder folder = m_folders[folderCount - 1 - folderIndex]; for (int fileIndex = 0, fileCount = folder.files.size(); fileIndex < fileCount; ++ fileIndex) { SpriterFile file = folder.files[fileCount - 1 - fileIndex]; SpriterObject spriterObject; spriterObject.id = objectId++; spriterObject.folderId = folder.id; spriterObject.fileId = file.id; spriterObject.x = file.x; spriterObject.y = -file.y; Bone *bone = 0; //qDebug() << "file layername" << file.layerName; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("bone(")) { int start = file.layerName.indexOf("bone(") + 5; int end = file.layerName.indexOf(')', start); QString boneName = file.layerName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // layer.name format: "base_name" if (!bone && m_rootBone) { bone = findBoneByName(m_rootBone, file.layerName); } // group.name format: "base_name bone(bone_name)" if (!bone && m_rootBone) { if (folder.groupName.contains("bone(")) { int start = folder.groupName.indexOf("bone(") + 5; int end = folder.groupName.indexOf(')', start); QString boneName = folder.groupName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // group.name format: "base_name" if (!bone) { bone = findBoneByName(m_rootBone, folder.groupName); } } if (!bone) { bone = m_rootBone; } if (bone) { spriterObject.bone = bone; spriterObject.localX = spriterObject.x - bone->x; spriterObject.localY = spriterObject.y - bone->y; } else { spriterObject.bone = 0; spriterObject.localX = spriterObject.x; spriterObject.localY = spriterObject.y; } spriterObject.localAngle = 0; spriterObject.localScaleX = 1.0; spriterObject.localScaleY = 1.0; SpriterSlot *slot = 0; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("slot(")) { int start = file.layerName.indexOf("slot(") + 5; int end = file.layerName.indexOf(')', start); slot = new SpriterSlot(); slot->name = file.layerName.mid(start, end - start); slot->defaultAttachmentFlag = file.layerName.contains("*"); } spriterObject.slot = slot; // qDebug() << "Created object" << spriterObject.id << spriterObject.folderId // << spriterObject.fileId << spriterObject.x << spriterObject.y // << spriterObject.localX << spriterObject.localY; m_objects.append(spriterObject); } } // Copy object transforms for (int i = 0; i < m_objects.size(); ++i) { m_objects[i].fixLocalX = m_objects[i].localX; m_objects[i].fixLocalY = m_objects[i].localY; m_objects[i].fixLocalAngle = m_objects[i].localAngle; m_objects[i].fixLocalScaleX = m_objects[i].localScaleX; m_objects[i].fixLocalScaleY = m_objects[i].localScaleY; } // Calculate bone angles if (m_rootBone) { copyBone(m_rootBone); fixBone(m_rootBone); } // Generate scml QDomDocument scml; fillScml(scml, fi.baseName()); io->write("\n"); io->write(scml.toString(4).toUtf8()); delete m_rootBone; return KisImportExportFilter::OK; } void KisSpriterExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Spriter"); } #include "kis_spriter_export.moc" diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.cpp b/plugins/paintops/gridbrush/kis_grid_paintop.cpp index 2eb2b2d28c..1f70729b0a 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop.cpp +++ b/plugins/paintops/gridbrush/kis_grid_paintop.cpp @@ -1,265 +1,265 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@gmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_grid_paintop.h" #include "kis_grid_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BENCHMARK #include #endif -KisGridPaintOp::KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) +KisGridPaintOp::KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP /*image*/) : KisPaintOp(painter) , m_settings(static_cast(const_cast(settings.data()))) , m_node(node) { m_properties.readOptionSetting(settings); m_colorProperties.fillProperties(settings); m_xSpacing = m_properties.gridWidth * m_properties.scale; m_ySpacing = m_properties.gridHeight * m_properties.scale; m_spacing = m_xSpacing; m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter->paintColor()); m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); #ifdef BENCHMARK m_count = m_total = 0; #endif } KisGridPaintOp::~KisGridPaintOp() { delete m_painter; } KisSpacingInformation KisGridPaintOp::paintAt(const KisPaintInformation& info) { #ifdef BENCHMARK QTime time; time.start(); #endif KisRandomSourceSP randomSource = info.randomSource(); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); m_dab->clear(); qreal gridWidth = m_properties.gridWidth * m_properties.scale * additionalScale; qreal gridHeight = m_properties.gridHeight * m_properties.scale * additionalScale; int divide; if (m_properties.pressureDivision) { divide = m_properties.divisionLevel * info.pressure(); } else { divide = m_properties.divisionLevel; } divide = qRound(m_properties.scale * divide); qreal posX = info.pos().x(); qreal posY = info.pos().y(); posX = posX - std::fmod(posX, gridWidth); posY = posY - std::fmod(posY, gridHeight); const QRectF dabRect(posX, posY, gridWidth, gridHeight); const QRect dabRectAligned = dabRect.toAlignedRect(); divide = qMax(1, divide); const qreal yStep = gridHeight / (qreal)divide; const qreal xStep = gridWidth / (qreal)divide; QRectF tile; KoColor color(painter()->paintColor()); QScopedPointer colorPicker; if (m_node) { colorPicker.reset(new KisCrossDeviceColorPicker(m_node->paintDevice(), color)); } qreal vertBorder = m_properties.vertBorder * additionalScale; qreal horzBorder = m_properties.horizBorder * additionalScale; if (m_properties.randomBorder) { if (vertBorder == horzBorder) { vertBorder = horzBorder = vertBorder * randomSource->generateNormalized(); } else { vertBorder *= randomSource->generateNormalized(); horzBorder *= randomSource->generateNormalized(); } } bool shouldColor = true; // fill the tile if (m_colorProperties.fillBackground) { m_dab->fill(dabRectAligned, painter()->backgroundColor()); } for (int y = 0; y < divide; y++) { for (int x = 0; x < divide; x++) { // determine the tile size tile = QRectF(dabRect.x() + x * xStep, dabRect.y() + y * yStep, xStep, yStep); tile.adjust(vertBorder, horzBorder, -vertBorder, -horzBorder); tile = tile.normalized(); // do color transformation if (shouldColor) { if (colorPicker && m_colorProperties.sampleInputColor) { colorPicker->pickOldColor(tile.center().x(), tile.center().y(), color.data()); } // mix the color with background color if (m_colorProperties.mixBgColor) { KoMixColorsOp * mixOp = source()->colorSpace()->mixColorsOp(); const quint8 *colors[2]; colors[0] = color.data(); colors[1] = painter()->backgroundColor().data(); qint16 colorWeights[2]; int MAX_16BIT = 255; qreal blend = info.pressure(); colorWeights[0] = static_cast(blend * MAX_16BIT); colorWeights[1] = static_cast((1.0 - blend) * MAX_16BIT); mixOp->mixColors(colors, colorWeights, 2, color.data()); } if (m_colorProperties.useRandomHSV) { QHash params; params["h"] = (m_colorProperties.hue / 180.0) * randomSource->generateNormalized(); params["s"] = (m_colorProperties.saturation / 100.0) * randomSource->generateNormalized(); params["v"] = (m_colorProperties.value / 100.0) * randomSource->generateNormalized(); KoColorTransformation* transfo; transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", params); transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option. transfo->setParameter(4, false);//sets the colorize to false. transfo->transform(color.data(), color.data() , 1); } if (m_colorProperties.useRandomOpacity) { const qreal alpha = randomSource->generateNormalized(); color.setOpacity(alpha); m_painter->setOpacity(qRound(alpha * OPACITY_OPAQUE_U8)); } if (!m_colorProperties.colorPerParticle) { shouldColor = false; } m_painter->setPaintColor(color); } // paint some element switch (m_properties.shape) { case 0: { m_painter->paintEllipse(tile); break; } case 1: { // anti-aliased version //m_painter->paintRect(tile); m_dab->fill(tile.topLeft().x(), tile.topLeft().y(), tile.width(), tile.height(), color.data()); break; } case 2: { m_painter->drawDDALine(tile.topRight(), tile.bottomLeft()); break; } case 3: { m_painter->drawLine(tile.topRight(), tile.bottomLeft()); break; } case 4: { m_painter->drawThickLine(tile.topRight(), tile.bottomLeft() , 1, 10); break; } default: { break; } } if (m_colorProperties.colorPerParticle){ color=painter()->paintColor();//reset color// } } } QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); #ifdef BENCHMARK int msec = time.elapsed(); dbgKrita << msec << " ms/dab " << "[average: " << m_total / (qreal)m_count << "]"; m_total += msec; m_count++; #endif return computeSpacing(additionalScale); } KisSpacingInformation KisGridPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return computeSpacing(KisLodTransform::lodToScale(painter()->device())); } KisSpacingInformation KisGridPaintOp::computeSpacing(qreal lodScale) const { return KisSpacingInformation(m_spacing * lodScale); } void KisGridProperties::readOptionSetting(const KisPropertiesConfigurationSP setting) { gridWidth = qMax(1, setting->getInt(GRID_WIDTH)); gridHeight = qMax(1, setting->getInt(GRID_HEIGHT)); divisionLevel = qMax(1, setting->getInt(GRID_DIVISION_LEVEL)); pressureDivision = setting->getBool(GRID_PRESSURE_DIVISION); randomBorder = setting->getBool(GRID_RANDOM_BORDER); scale = qMax(0.1, setting->getDouble(GRID_SCALE)); vertBorder = setting->getDouble(GRID_VERTICAL_BORDER); horizBorder = setting->getDouble(GRID_HORIZONTAL_BORDER); shape = setting->getInt(GRIDSHAPE_SHAPE); } diff --git a/plugins/paintops/hatching/kis_hatching_paintop.cpp b/plugins/paintops/hatching/kis_hatching_paintop.cpp index e9bc73e5b0..5d70a05a08 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop.cpp @@ -1,213 +1,213 @@ /* * Copyright (c) 2008,2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hatching_paintop.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -KisHatchingPaintOp::KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) +KisHatchingPaintOp::KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP /*image*/) : KisBrushBasedPaintOp(settings, painter) { Q_UNUSED(node); m_settings = new KisHatchingPaintOpSettings(); static_cast(settings.data())->initializeTwin(m_settings); m_hatchingBrush = new HatchingBrush(m_settings); m_angleOption.readOptionSetting(settings); m_crosshatchingOption.readOptionSetting(settings); m_separationOption.readOptionSetting(settings); m_thicknessOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_angleOption.resetAllSensors(); m_crosshatchingOption.resetAllSensors(); m_separationOption.resetAllSensors(); m_thicknessOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } KisHatchingPaintOp::~KisHatchingPaintOp() { delete m_hatchingBrush; } KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& info) { //------START SIMPLE ERROR CATCHING------- if (!painter()->device()) return KisSpacingInformation(1.0); if (!m_hatchedDab) m_hatchedDab = source()->createCompositionSourceDevice(); else m_hatchedDab->clear(); //Simple convenience renaming, I'm thinking of removing these inherited quirks KisBrushSP brush = m_brush; KisPaintDeviceSP device = painter()->device(); //Macro to catch errors Q_ASSERT(brush); //----------SIMPLE error catching code, maybe it's not even needed------ if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); //SENSOR-depending settings m_settings->anglesensorvalue = m_angleOption.apply(info); m_settings->crosshatchingsensorvalue = m_crosshatchingOption.apply(info); m_settings->separationsensorvalue = m_separationOption.apply(info); m_settings->thicknesssensorvalue = m_thicknessOption.apply(info); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const double scale = additionalScale * m_sizeOption.apply(info); if ((scale * brush->width()) <= 0.01 || (scale * brush->height()) <= 0.01) return KisSpacingInformation(1.0); KisDabShape shape(scale, 1.0, 0.0); quint8 origOpacity = m_opacityOption.apply(painter(), info); /*----Fetch the Dab----*/ static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP maskDab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); // sanity check KIS_ASSERT_RECOVER_NOOP(dstRect.size() == maskDab->bounds().size()); /*-----Convenient renaming for the limits of the maskDab, this will be used to hatch a dab of just the right size------*/ qint32 x, y, sw, sh; dstRect.getRect(&x, &y, &sw, &sh); //------This If_block pre-fills the future m_hatchedDab with a pretty backgroundColor if (m_settings->opaquebackground) { KoColor aersh = painter()->backgroundColor(); m_hatchedDab->fill(0, 0, (sw - 1), (sh - 1), aersh.data()); //this plus yellow background = french fry brush } /* If block describing how to stack hatching passes to generate crosshatching according to user specifications */ if (m_settings->enabledcurvecrosshatching) { if (m_settings->perpendicular) { if (m_settings->crosshatchingsensorvalue > 0.5) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->crosshatchingsensorvalue) * 360), painter()->paintColor(), additionalScale); } } else { if (m_settings->perpendicular) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-10), painter()->paintColor(), additionalScale); } } if (m_settings->enabledcurveangle) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->anglesensorvalue)*360+m_settings->angle), painter()->paintColor(), additionalScale); // The base hatch... unless moiré or angle if (!m_settings->moirepattern && !m_settings->enabledcurveangle) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, m_settings->angle, painter()->paintColor(), additionalScale); // The most important line, the one that paints to the screen. painter()->bitBltWithFixedSelection(x, y, m_hatchedDab, maskDab, sw, sh); painter()->renderMirrorMaskSafe(QRect(QPoint(x, y), QSize(sw, sh)), m_hatchedDab, 0, 0, maskDab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale); } KisSpacingInformation KisHatchingPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = KisLodTransform::lodToScale(painter()->device()) * m_sizeOption.apply(info); return effectiveSpacing(scale); } double KisHatchingPaintOp::spinAngle(double spin) { double tempangle = m_settings->angle + spin; qint8 factor = 1; if (tempangle < 0) factor = -1; tempangle = fabs(fmod(tempangle, 180)); if ((tempangle >= 0) && (tempangle <= 90)) return factor * tempangle; else if ((tempangle > 90) && (tempangle <= 180)) return factor * -(180 - tempangle); return 0; // this should never be executed except if NAN } diff --git a/plugins/paintops/libpaintop/KisDabCacheUtils.cpp b/plugins/paintops/libpaintop/KisDabCacheUtils.cpp index a17bec115f..3479056e5a 100644 --- a/plugins/paintops/libpaintop/KisDabCacheUtils.cpp +++ b/plugins/paintops/libpaintop/KisDabCacheUtils.cpp @@ -1,118 +1,118 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisDabCacheUtils.h" #include "kis_brush.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_color_source.h" #include #include #include namespace KisDabCacheUtils { DabRenderingResources::DabRenderingResources() { } DabRenderingResources::~DabRenderingResources() { } void DabRenderingResources::syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info) { brush->prepareForSeqNo(info, seqNo); } QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect, const QSize &realDabSize) { int diffX = (realDabSize.width() - dabRect.width()) / 2; int diffY = (realDabSize.height() - dabRect.height()) / 2; return QRect(dabRect.x() - diffX, dabRect.y() - diffY, realDabSize.width() , realDabSize.height()); } void generateDab(const DabGenerationInfo &di, DabRenderingResources *resources, KisFixedPaintDeviceSP *dab) { KIS_SAFE_ASSERT_RECOVER_RETURN(*dab); const KoColorSpace *cs = (*dab)->colorSpace(); if (resources->brush->brushType() == IMAGE || resources->brush->brushType() == PIPE_IMAGE) { *dab = resources->brush->paintDevice(cs, di.shape, di.info, di.subPixel.x(), di.subPixel.y()); } else if (di.solidColorFill) { resources->brush->mask(*dab, di.paintColor, di.shape, di.info, di.subPixel.x(), di.subPixel.y(), di.softnessFactor); } else { if (!resources->colorSourceDevice || *cs != *resources->colorSourceDevice->colorSpace()) { resources->colorSourceDevice = new KisPaintDevice(cs); } else { resources->colorSourceDevice->clear(); } QRect maskRect(QPoint(), di.dstDabRect.size()); resources->colorSource->colorize(resources->colorSourceDevice, maskRect, di.info.pos().toPoint()); - delete resources->colorSourceDevice->convertTo(cs); + resources->colorSourceDevice->convertTo(cs); resources->brush->mask(*dab, resources->colorSourceDevice, di.shape, di.info, di.subPixel.x(), di.subPixel.y(), di.softnessFactor); } if (!di.mirrorProperties.isEmpty()) { (*dab)->mirror(di.mirrorProperties.horizontalMirror, di.mirrorProperties.verticalMirror); } } void postProcessDab(KisFixedPaintDeviceSP dab, const QPoint &dabTopLeft, const KisPaintInformation& info, DabRenderingResources *resources) { if (resources->sharpnessOption) { - resources->sharpnessOption->applyThreshold(dab); + resources->sharpnessOption->applyThreshold(dab, info); } if (resources->textureOption) { resources->textureOption->apply(dab, dabTopLeft, info); } } } diff --git a/plugins/paintops/libpaintop/forms/wdgcurveoption.ui b/plugins/paintops/libpaintop/forms/wdgcurveoption.ui index 0dd0a426f2..ca20e16636 100644 --- a/plugins/paintops/libpaintop/forms/wdgcurveoption.ui +++ b/plugins/paintops/libpaintop/forms/wdgcurveoption.ui @@ -1,568 +1,573 @@ WdgCurveOption 0 0 503 464 15 0 0 20 20 16777215 16777215 190 0 Enable Pen Settings 24 24 16 16 24 24 16 16 24 24 16 16 24 24 16 16 24 24 16 16 24 24 16 16 24 24 16 16 24 24 16 16 Ctrl+S Qt::Horizontal 2 2 0 0 190 0 160 16777215 Qt::Horizontal 2 2 Qt::Vertical 2 2 Share curve across all settings true 0 0 TextLabel Qt::Vertical 10 10 0 0 + + + 9 + + false Qt::Horizontal 40 20 Qt::Vertical 20 40 0 0 TextLabel 0 0 TextLabel Qt::Horizontal 40 20 0 0 0 0 9 true Qt::Horizontal 40 20 0 0 TextLabel 1 0 200 200 <html><head/><body> <p>Curve calculation mode changes how 2 or more curve works together<br/></p> <p> multiply (default): all values from curves multiplies </p><p>(0.8 pressure) * (0.5 speed) = 0.4 <br/></p> <p> addition: all values from curves adds</p><p>(0.6 pressure) + (0.3 speed) = 0.9<br/></p> <p> maximum value</p><p>(0.7 pressure), (0.3 speed) = 0.7<br/></p> <p> minimum value </p><p>(0.7 pressure), (0.3 speed) = 0.3<br/></p> <p> difference between min and max values</p><p>(0.8 pressure), (0.3 speed), (0.6 fade) = 0.5</p></body></html> Curves calculation mode: <html><head/><body> <p>Curve calculation mode changes how 2 or more curve works together<br/></p> <p> multiply (default): all values from curves multiplies </p><p>(0.8 pressure) * (0.5 speed) = 0.4 <br/></p> <p> addition: all values from curves adds</p><p>(0.6 pressure) + (0.3 speed) = 0.9<br/></p> <p> maximum value</p><p>(0.7 pressure), (0.3 speed) = 0.7<br/></p> <p> minimum value </p><p>(0.7 pressure), (0.3 speed) = 0.3<br/></p> <p> difference between min and max values</p><p>(0.8 pressure), (0.3 speed), (0.6 fade) = 0.5</p></body></html> false multiply addition maximum minimum difference - - KisCurveWidget - -
widgets/kis_curve_widget.h
- 1 -
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
+ + KisCurveWidget + +
widgets/kis_curve_widget.h
+ 1 +
KisMultiSensorsSelector QWidget
kis_multi_sensors_selector.h
1
diff --git a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp index 62303b796a..cd4f76d048 100644 --- a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp @@ -1,159 +1,159 @@ /* * Copyright (c) 2005 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2013 Somsubhra Bairi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_clipboard_brush_widget.h" #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_clipboard.h" #include "kis_paint_device.h" #include "kis_gbr_brush.h" #include "KisBrushServerProvider.h" -KisClipboardBrushWidget::KisClipboardBrushWidget(QWidget *parent, const QString &caption, KisImageWSP image) +KisClipboardBrushWidget::KisClipboardBrushWidget(QWidget *parent, const QString &caption, KisImageWSP /*image*/) : KisWdgClipboardBrush(parent) { setWindowTitle(caption); preview->setScaledContents(true); preview->setFixedSize(preview->size()); preview->setStyleSheet("border: 2px solid #222; border-radius: 4px; padding: 5px; font: normal 10px;"); m_rServer = KisBrushServerProvider::instance()->brushServer(); m_brush = 0; m_clipboard = KisClipboard::instance(); connect(m_clipboard, SIGNAL(clipChanged()), this, SLOT(slotCreateBrush())); connect(colorAsmask, SIGNAL(toggled(bool)), this, SLOT(slotUpdateUseColorAsMask(bool))); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotAddPredefined())); spacingWidget->setSpacing(true, 1.0); connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); } KisClipboardBrushWidget::~KisClipboardBrushWidget() { } void KisClipboardBrushWidget::slotCreateBrush() { // do nothing if it's hidden otherwise it can break the active brush is something is copied if (m_clipboard->hasClip() && !isHidden()) { pd = m_clipboard->clip(QRect(0, 0, 0, 0), false); //Weird! Don't know how this works! if (pd) { QRect rc = pd->exactBounds(); m_brush = KisBrushSP(new KisGbrBrush(pd, rc.x(), rc.y(), rc.width(), rc.height())); m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush->setFilename(TEMPORARY_CLIPBOARD_BRUSH_FILENAME); m_brush->setName(TEMPORARY_CLIPBOARD_BRUSH_NAME); m_brush->setValid(true); preview->setPixmap(QPixmap::fromImage(m_brush->image())); } } else { preview->setText(i18n("Nothing copied\n to Clipboard")); } if (!m_brush) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } else { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); colorAsmask->setChecked(true); // initializing this has to happen here since we need a valid brush for it to work } } void KisClipboardBrushWidget::slotSpacingChanged() { if (m_brush) { m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); } } void KisClipboardBrushWidget::showEvent(QShowEvent *) { slotCreateBrush(); } void KisClipboardBrushWidget::slotUpdateUseColorAsMask(bool useColorAsMask) { if (m_brush) { static_cast(m_brush.data())->setUseColorAsMask(useColorAsMask); preview->setPixmap(QPixmap::fromImage(m_brush->brushTipImage())); } } void KisClipboardBrushWidget::slotAddPredefined() { if(!m_brush) return; QString dir = KoResourcePaths::saveLocation("data", ResourceType::Brushes); QString extension = ".gbr"; QString name = nameEdit->text(); QString tempFileName; QFileInfo fileInfo; fileInfo.setFile(dir + name + extension); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(dir + name + QString("%1").arg(i) + extension); i++; } tempFileName = fileInfo.filePath(); if (m_rServer) { KisGbrBrushSP resource = m_brush->clone().dynamicCast(); resource->setFilename(tempFileName); if (nameEdit->text().isEmpty()) { resource->setName(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm")); } else { resource->setName(name); } if (colorAsmask->isChecked()) { resource->makeMaskImage(); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } close(); } #include "moc_kis_clipboard_brush_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp index 8889e11a40..3bec56d711 100644 --- a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp @@ -1,109 +1,119 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_pressure_sharpness_option.h" #include #include #include #include #include #include KisPressureSharpnessOption::KisPressureSharpnessOption() : KisCurveOption("Sharpness", KisPaintOpOption::GENERAL, false) + , m_softness(0) { } void KisPressureSharpnessOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { KisCurveOption::writeOptionSetting(setting); - setting->setProperty(SHARPNESS_THRESHOLD, m_threshold); + setting->setProperty(SHARPNESS_SOFTNESS, m_softness); } void KisPressureSharpnessOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { KisCurveOption::readOptionSetting(setting); - m_threshold = setting->getInt(SHARPNESS_THRESHOLD, 4); + m_softness = quint32(setting->getInt(SHARPNESS_SOFTNESS)); // backward compatibility: test for a "sharpness factor" property // and use this value if it does exist - if (setting->hasProperty(SHARPNESS_FACTOR) && !setting->hasProperty("SharpnessValue")) + if (setting->hasProperty(SHARPNESS_FACTOR) && !setting->hasProperty("SharpnessValue")) { KisCurveOption::setValue(setting->getDouble(SHARPNESS_FACTOR)); + m_softness = quint32(setting->getDouble(SHARPNESS_FACTOR) * 100); + } + } void KisPressureSharpnessOption::apply(const KisPaintInformation &info, const QPointF &pt, qint32 &x, qint32 &y, qreal &xFraction, qreal &yFraction) const { if (!isChecked() || KisCurveOption::value() == 0.0) { // brush KisPaintOp::splitCoordinate(pt.x(), &x, &xFraction); KisPaintOp::splitCoordinate(pt.y(), &y, &yFraction); } else { qreal processedSharpness = computeSizeLikeValue(info); if (processedSharpness == 1.0) { // pen xFraction = 0.0; yFraction = 0.0; x = qRound(pt.x()); y = qRound(pt.y()); } else { // something in between qint32 xi = qRound(pt.x()); qint32 yi = qRound(pt.y()); qreal xf = processedSharpness * xi + (1.0 - processedSharpness) * pt.x(); qreal yf = processedSharpness * yi + (1.0 - processedSharpness) * pt.y(); KisPaintOp::splitCoordinate(xf, &x, &xFraction); KisPaintOp::splitCoordinate(yf, &y, &yFraction); } } } -void KisPressureSharpnessOption::applyThreshold(KisFixedPaintDeviceSP dab) +void KisPressureSharpnessOption::applyThreshold(KisFixedPaintDeviceSP dab, const KisPaintInformation & info) { if (!isChecked()) return; const KoColorSpace * cs = dab->colorSpace(); // Set all alpha > opaque/2 to opaque, the rest to transparent. // XXX: Using 4/10 as the 1x1 circle brush paints nothing with 0.5. quint8* dabPointer = dab->data(); QRect rc = dab->bounds(); - int pixelSize = dab->pixelSize(); + qreal threshold = computeSizeLikeValue(info); + + quint32 pixelSize = dab->pixelSize(); int pixelCount = rc.width() * rc.height(); + quint32 tolerance = quint32(OPACITY_OPAQUE_U8 - (threshold * OPACITY_OPAQUE_U8)); + for (int i = 0; i < pixelCount; i++) { - quint8 alpha = cs->opacityU8(dabPointer); + quint8 opacity = cs->opacityU8(dabPointer); - if (alpha < (m_threshold * OPACITY_OPAQUE_U8) / 100) { - cs->setOpacity(dabPointer, OPACITY_TRANSPARENT_U8, 1); - } - else { + // Check what pixel goes sharp + if (opacity > (tolerance) ) { cs->setOpacity(dabPointer, OPACITY_OPAQUE_U8, 1); + } else { + // keep original value if in soft range + if (opacity <= (100 - m_softness) * tolerance / 100) { + cs->setOpacity(dabPointer, OPACITY_TRANSPARENT_U8, 1); + } } - dabPointer += pixelSize; } } diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h index 7111f8d14c..9de0cc19da 100644 --- a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h +++ b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h @@ -1,72 +1,71 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PRESSURE_SHARPNESS_OPTION_H #define KIS_PRESSURE_SHARPNESS_OPTION_H #include "kis_curve_option.h" #include #include #include - const QString SHARPNESS_FACTOR = "Sharpness/factor"; -const QString SHARPNESS_THRESHOLD = "Sharpness/threshold"; +const QString SHARPNESS_SOFTNESS = "Sharpness/softness"; /** * This option is responsible to mimic pencil effect from former Pixel Pencil brush engine.auto */ class PAINTOP_EXPORT KisPressureSharpnessOption : public KisCurveOption { public: KisPressureSharpnessOption(); /** * First part of the sharpness is the coordinates: in pen mode they are integers without fractions */ void apply(const KisPaintInformation &info, const QPointF &pt, qint32 &x, qint32 &y, qreal &xFraction, qreal &yFraction) const; /** * Apply threshold specified by user */ - void applyThreshold(KisFixedPaintDeviceSP dab); + void applyThreshold(KisFixedPaintDeviceSP dab, const KisPaintInformation &info); void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; /// threshold has 100 levels (like opacity) void setThreshold(qint32 threshold) { - m_threshold = qBound(0, threshold, 100); + m_softness = qBound(0, quint32(threshold), 100); } qint32 threshold() { - return m_threshold; + return qint32(m_softness); } void setSharpnessFactor(qreal factor) { KisCurveOption::setValue(factor); } qreal sharpnessFactor() { return KisCurveOption::value(); } private: - qint32 m_threshold {40}; + quint32 m_softness {0}; }; #endif diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.cpp b/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.cpp index 440513d00f..ce6f810ddc 100644 --- a/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.cpp @@ -1,68 +1,75 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "kis_curve_option_widget.h" #include "kis_pressure_sharpness_option.h" #include "kis_pressure_sharpness_option_widget.h" KisPressureSharpnessOptionWidget::KisPressureSharpnessOptionWidget(): KisCurveOptionWidget(new KisPressureSharpnessOption(), i18n("0.0"), i18n("1.0")) { setObjectName("KisPressureSharpnessOptionWidget"); - QLabel* thresholdLbl = new QLabel(i18n("Threshold:")); - - m_threshold = new KisSliderSpinBox(); - m_threshold->setRange(1, 100); - m_threshold->setValue(40); - m_threshold->setSingleStep(1); + QLabel* thresholdLbl = new QLabel(i18n("Soften edge:")); + m_softenedge = new KisSliderSpinBox(); + m_softenedge->setRange(0, 100); + m_softenedge->setValue(0); // Sets old behaviour + m_softenedge->setSingleStep(1); QHBoxLayout* hl = new QHBoxLayout; + hl->setMargin(9); hl->addWidget(thresholdLbl); - hl->addWidget(m_threshold, 1); + hl->addWidget(m_softenedge, 1); QVBoxLayout* vl = new QVBoxLayout; vl->setMargin(0); vl->addLayout(hl); vl->addWidget(KisCurveOptionWidget::curveWidget()); QWidget* w = new QWidget; w->setLayout(vl); - KisCurveOptionWidget::setConfigurationPage(w); + connect(m_softenedge, SIGNAL(valueChanged(int)), SLOT(setThreshold(int))); + + setConfigurationPage(w); - connect(m_threshold, SIGNAL(valueChanged(int)), this, SLOT(setThreshold(int))); - setThreshold(m_threshold->value()); + setThreshold(m_softenedge->value()); +} + +void KisPressureSharpnessOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) +{ + KisCurveOptionWidget::readOptionSetting(setting); + m_softenedge->setValue(static_cast(curveOption())->threshold()); } void KisPressureSharpnessOptionWidget::setThreshold(int threshold) { - static_cast(KisCurveOptionWidget::curveOption())->setThreshold(threshold); + static_cast(curveOption())->setThreshold(threshold); emitSettingChanged(); } diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.h b/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.h index 9230c7e83e..f6c368896d 100644 --- a/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.h +++ b/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.h @@ -1,42 +1,43 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PRESSURE_SHARPNESS_OPTION_WIDGET_H #define KIS_PRESSURE_SHARPNESS_OPTION_WIDGET_H #include "kis_curve_option_widget.h" class KisSliderSpinBox; - class PAINTOP_EXPORT KisPressureSharpnessOptionWidget : public KisCurveOptionWidget { Q_OBJECT public: KisPressureSharpnessOptionWidget(); + void readOptionSetting(const KisPropertiesConfigurationSP setting) override; + private Q_SLOTS: void setThreshold(int threshold); private: - KisSliderSpinBox* m_threshold; + KisSliderSpinBox* m_softenedge; }; #endif // KIS_PRESSURE_SHARPNESS_OPTION_WIDGET_H diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp index 00a71b69a0..3b82b95657 100644 --- a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp @@ -1,178 +1,178 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_roundmarkerop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_marker_painter.h" #include "kis_paintop_utils.h" -KisRoundMarkerOp::KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) +KisRoundMarkerOp::KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP /*image*/) : KisPaintOp(painter) , m_firstRun(true) , m_lastRadius(1.0) { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_markerOption.readOptionSetting(*settings); m_sizeOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_spacingOption.resetAllSensors(); } KisRoundMarkerOp::~KisRoundMarkerOp() { } KisSpacingInformation KisRoundMarkerOp::paintAt(const KisPaintInformation& info) { // Simple error catching if (!painter()->device()) { return KisSpacingInformation(1.0); } // get the scaling factor calculated by the size option const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = m_sizeOption.apply(info) * lodScale; const qreal rotation = 0; // TODO const qreal diameter = m_markerOption.diameter * scale; qreal radius = 0.5 * diameter; if (KisPaintOpUtils::checkSizeTooSmall(scale, diameter, diameter)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF pos = info.pos(); KisMarkerPainter gc(painter()->device(), painter()->paintColor()); if (m_firstRun) { const QVector points = painter()->calculateAllMirroredPoints(pos); Q_FOREACH(const QPointF &pt, points) { gc.fillFullCircle(pt, radius); } } else { const QVector> pairs = painter()->calculateAllMirroredPoints(qMakePair(m_lastPaintPos, pos)); Q_FOREACH(const auto &pair, pairs) { gc.fillCirclesDiff(pair.first, m_lastRadius, pair.second, radius); } } m_firstRun = false; m_lastPaintPos = pos; m_lastRadius = radius; QRectF dirtyRect(pos.x() - radius, pos.y() - radius, 2 * radius, 2 * radius); dirtyRect = kisGrowRect(dirtyRect, 1); const QVector allDirtyRects = painter()->calculateAllMirroredRects(dirtyRect.toAlignedRect()); painter()->addDirtyRects(allDirtyRects); // QPointF scatteredPos = // m_scatterOption.apply(info, // brush->maskWidth(shape, 0, 0, info), // brush->maskHeight(shape, 0, 0, info)); //updateMask(info, scale, rotation, scatteredPos); //QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ //QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); //m_lastPaintPos = newCenterPos; KisSpacingInformation spacingInfo = computeSpacing(info, diameter); if (m_firstRun) { m_firstRun = false; return spacingInfo; } return spacingInfo; } KisSpacingInformation KisRoundMarkerOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal diameter = m_markerOption.diameter * m_sizeOption.apply(info) * lodScale; return computeSpacing(info, diameter); } KisSpacingInformation KisRoundMarkerOp::computeSpacing(const KisPaintInformation &info, qreal diameter) const { const qreal rotation = 0; // TODO const bool axesFlipped = false; // TODO qreal extraSpacingScale = 1.0; if (m_spacingOption.isChecked()) { extraSpacingScale = m_spacingOption.apply(info); } return KisPaintOpUtils::effectiveSpacing(diameter, diameter, extraSpacingScale, true, true, rotation, axesFlipped, m_markerOption.spacing, m_markerOption.use_auto_spacing, m_markerOption.auto_spacing_coeff, KisLodTransform::lodToScale(painter()->device())); } diff --git a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index 9bb36b9fdf..9dfd90bac5 100644 --- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,55 +1,55 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=assignprofiledialog X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Assign Profile to Image Name[ar]=إسناد اللاحات إلى الصور Name[ca]=Assigna un perfil a una imatge Name[ca@valencia]=Assigna un perfil a una imatge Name[cs]=Přiřadit obrázku profil Name[el]=Αντιστοίχιση προφίλ σε εικόνα Name[en_GB]=Assign Profile to Image Name[es]=Asignar perfil a imagen Name[eu]=Esleitu profila irudiari Name[fi]=Liitä kuvaan profiili Name[fr]=Attribuer un profil à l'image Name[gl]=Asignar un perfil á imaxe Name[is]=Úthluta litasniði á myndina Name[it]=Assegna profilo a immagine Name[nl]=Profiel aan afbeelding toewijzen Name[nn]=Tildel profil til bilete Name[pl]=Przypisz profil do obrazu Name[pt]=Atribuir um Perfil à Imagem Name[pt_BR]=Atribuir perfil a imagem Name[sv]=Tilldela profil till bild Name[tr]=Görüntüye Profil Ata Name[uk]=Призначити профіль до зображення Name[x-test]=xxAssign Profile to Imagexx -Name[zh_CN]=为图像指定色彩配置文件 +Name[zh_CN]=为图像指定特性文件 Name[zh_TW]=指定設定檔到圖像 Comment=Assign a profile to an image without converting it. Comment[ar]=أسنِد لاحة إلى صورة دون تحويلها. Comment[ca]=Assigna un perfil a una imatge sense convertir-la. Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la. Comment[el]=Αντιστοιχίζει ένα προφίλ σε μια εικόνα χωρίς μετατροπή. Comment[en_GB]=Assign a profile to an image without converting it. Comment[es]=Asignar un perfil a una imagen sin convertirla. Comment[eu]=Esleitu profil bat irudi bati hura bihurtu gabe. Comment[fi]=Liitä kuvaan profiili muuntamatta kuvaa Comment[fr]=Attribuer un profil à une image sans la convertir. Comment[gl]=Asignar un perfil a unha imaxe sen convertela. Comment[is]=Úthluta litasniði á myndina án þess að umbreyta henni. Comment[it]=Assegna un profilo a un'immagine senza convertirla. Comment[nl]=Een profiel aan een afbeelding toewijzen zonder het te converteren. Comment[nn]=Tildel profil til eit bilete utan å konvertera det. Comment[pl]=Przypisz profil do obrazu bez jego przekształcania. Comment[pt]=Atribui um perfil à imagem sem a converter. Comment[pt_BR]=Atribui um perfil para uma imagem sem convertê-la. Comment[sv]=Tilldela en profil till en bild utan att konvertera den. Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata. Comment[uk]=Призначити профіль до зображення без його перетворення. Comment[x-test]=xxAssign a profile to an image without converting it.xx -Comment[zh_CN]=仅为图像指定色彩配置文件,不转换其色彩空间 +Comment[zh_CN]=仅为图像指定特性文件,不转换其色彩空间 Comment[zh_TW]=將設定檔指定給圖像,而不進行轉換。 diff --git a/plugins/python/scripter/kritapykrita_scripter.desktop b/plugins/python/scripter/kritapykrita_scripter.desktop index 4594a56abf..4118540618 100644 --- a/plugins/python/scripter/kritapykrita_scripter.desktop +++ b/plugins/python/scripter/kritapykrita_scripter.desktop @@ -1,46 +1,46 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scripter X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Scripter Name[ca]=Scripter Name[ca@valencia]=Scripter Name[de]=Scripter Name[el]=Σενάρια Name[en_GB]=Scripter Name[es]=Guionador Name[eu]=Script egilea Name[fr]=Scripter Name[gl]=Executor de scripts Name[it]=Scripter Name[nl]=Scriptmaker Name[nn]=Skriptkøyrer Name[pl]=Skrypter Name[pt]=Programador Name[sv]=Skriptgenerator Name[tr]=Betik yazarı Name[uk]=Скриптер Name[x-test]=xxScripterxx -Name[zh_CN]=脚本编写器 +Name[zh_CN]=脚本工具 Name[zh_TW]=腳本編寫者 Comment=Plugin to execute ad-hoc Python code Comment[ca]=Connector per executar codi «ad hoc» en Python Comment[ca@valencia]=Connector per executar codi «ad hoc» en Python Comment[el]=Πρόσθετο για την εκτέλεση συγκεκριμένου κώδικα Python Comment[en_GB]=Plugin to execute ad-hoc Python code Comment[es]=Complemento para ejecutar código Python a medida Comment[eu]=Berariaz egindako Python kodea exekutatzeko plugina Comment[fi]=Liitännäinen satunnaisen Python-koodin suorittamiseksi Comment[gl]=Complemento para executar código de Python escrito no momento. Comment[it]=Estensione per eseguire ad-hoc codice Python Comment[nl]=Plug-in om ad-hoc Python code uit te voeren Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc Comment[pt]='Plugin' para executar código em Python arbitrário Comment[sv]=Insticksprogram för att köra godtycklig Python-kod Comment[tr]=Geçici Python kodu çalıştırmak için eklenti Comment[uk]=Додаток для виконання апріорного коду Python Comment[x-test]=xxPlugin to execute ad-hoc Python codexx Comment[zh_CN]=用于执行当场编写的 Python 代码的插件 Comment[zh_TW]=外掛程式,用於執行特定 Python 程式碼