File indexing completed on 2024-05-12 15:58:52

0001 /*
0002  *  SPDX-FileCopyrightText: 2021 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisOverlayPaintDeviceWrapper.h"
0008 
0009 #include "kis_paint_device.h"
0010 #include <KoColor.h>
0011 #include "KisRectsGrid.h"
0012 #include "kis_painter.h"
0013 #include "KoColorModelStandardIds.h"
0014 #include "KisFastDeviceProcessingUtils.h"
0015 #include "KisRegion.h"
0016 #include "kis_wrapped_rect.h"
0017 #include <KoOptimizedPixelDataScalerU8ToU16Factory.h>
0018 #include <kundo2command.h>
0019 #include <memory>
0020 #include <kis_transaction.h>
0021 #include "kis_command_utils.h"
0022 #include "KoColorProfile.h"
0023 
0024 struct KisChangeOverlayWrapperCommand;
0025 
0026 struct KisOverlayPaintDeviceWrapper::Private
0027 {
0028     KisPaintDeviceSP source;
0029     QVector<KisPaintDeviceSP> overlays;
0030     KisRectsGrid grid;
0031     bool usePreciseMode = false;
0032     QScopedPointer<KoOptimizedPixelDataScalerU8ToU16Base> scaler;
0033     KisPaintDeviceSP externalDestination;
0034 
0035     QScopedPointer<KUndo2Command> rootTransactionData;
0036     KisChangeOverlayWrapperCommand *changeOverlayCommand;
0037     std::vector<std::unique_ptr<KisTransaction>> overlayTransactions;
0038     QSharedPointer<KisRectsGrid> previousGrid;
0039 };
0040 
0041 struct KisChangeOverlayWrapperCommand : public KUndo2Command
0042 {
0043     KisChangeOverlayWrapperCommand(KisOverlayPaintDeviceWrapper::Private *d, KUndo2Command *parent = 0)
0044             : KUndo2Command(parent)
0045             , m_d(d)
0046     {
0047     }
0048 
0049     void undo() override {
0050         KUndo2Command::undo();
0051         m_d->grid = *m_oldRectsGrid;
0052         m_d->previousGrid = m_oldRectsGrid;
0053     }
0054 
0055     void redo() override {
0056         KUndo2Command::redo();
0057         m_d->grid = *m_newRectsGrid;
0058         m_d->previousGrid = m_newRectsGrid;
0059     }
0060 
0061     QSharedPointer<KisRectsGrid> m_oldRectsGrid;
0062     QSharedPointer<KisRectsGrid> m_newRectsGrid;
0063     KisOverlayPaintDeviceWrapper::Private *m_d;
0064 };
0065 
0066 KisOverlayPaintDeviceWrapper::KisOverlayPaintDeviceWrapper(KisPaintDeviceSP source, int numOverlays, KisOverlayPaintDeviceWrapper::OverlayMode mode, const KoColorSpace *forcedOverlayColorSpace)
0067     : m_d(new Private())
0068 {
0069     m_d->source = source;
0070 
0071     const KoColorSpace *overlayColorSpace = source->compositionSourceColorSpace();
0072 
0073     if (forcedOverlayColorSpace) {
0074         overlayColorSpace = forcedOverlayColorSpace;
0075     } else {
0076         const bool usePreciseMode = mode == PreciseMode || mode == LazyPreciseMode;
0077 
0078         if (usePreciseMode &&
0079             overlayColorSpace->colorDepthId() == Integer8BitsColorDepthID) {
0080 
0081             overlayColorSpace =
0082                     KoColorSpaceRegistry::instance()->colorSpace(
0083                         overlayColorSpace->colorModelId().id(),
0084                         Integer16BitsColorDepthID.id(),
0085                         overlayColorSpace->profile());
0086         }
0087     }
0088 
0089     m_d->usePreciseMode = *source->colorSpace() != *overlayColorSpace;
0090 
0091     if (source->colorSpace()->colorModelId() == RGBAColorModelID &&
0092         source->colorSpace()->colorDepthId() == Integer8BitsColorDepthID &&
0093         overlayColorSpace->colorModelId() == RGBAColorModelID &&
0094         overlayColorSpace->colorDepthId() == Integer16BitsColorDepthID &&
0095         *source->colorSpace()->profile() == *overlayColorSpace->profile()) {
0096 
0097         m_d->scaler.reset(KoOptimizedPixelDataScalerU8ToU16Factory::createRgbaScaler());
0098 
0099     } else if (source->colorSpace()->colorModelId() == CMYKAColorModelID &&
0100         source->colorSpace()->colorDepthId() == Integer8BitsColorDepthID &&
0101         overlayColorSpace->colorModelId() == CMYKAColorModelID &&
0102         overlayColorSpace->colorDepthId() == Integer16BitsColorDepthID &&
0103         *source->colorSpace()->profile() == *overlayColorSpace->profile()) {
0104 
0105         m_d->scaler.reset(KoOptimizedPixelDataScalerU8ToU16Factory::createCmykaScaler());
0106 
0107     } else if (source->colorSpace()->colorModelId() == YCbCrAColorModelID &&
0108                source->colorSpace()->colorDepthId() == Integer8BitsColorDepthID &&
0109                overlayColorSpace->colorModelId() == YCbCrAColorModelID &&
0110                overlayColorSpace->colorDepthId() == Integer16BitsColorDepthID &&
0111                *source->colorSpace()->profile() == *overlayColorSpace->profile()) {
0112 
0113         m_d->scaler.reset(KoOptimizedPixelDataScalerU8ToU16Factory::createRgbaScaler());
0114 
0115     }
0116 
0117     if (!m_d->usePreciseMode && mode == LazyPreciseMode && numOverlays == 1) {
0118         return;
0119     }
0120 
0121     for (int i = 0; i < numOverlays; i++) {
0122         KisPaintDeviceSP overlay = new KisPaintDevice(overlayColorSpace);
0123         overlay->setDefaultPixel(source->defaultPixel().convertedTo(overlayColorSpace));
0124         overlay->setDefaultBounds(source->defaultBounds());
0125         overlay->moveTo(source->offset());
0126 
0127         m_d->overlays.append(overlay);
0128     }
0129 }
0130 
0131 KisOverlayPaintDeviceWrapper::~KisOverlayPaintDeviceWrapper()
0132 {
0133 }
0134 
0135 void KisOverlayPaintDeviceWrapper::setExternalDestination(KisPaintDeviceSP device)
0136 {
0137     m_d->externalDestination = device;
0138 }
0139 
0140 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::externalDestination() const
0141 {
0142     return m_d->externalDestination;
0143 }
0144 
0145 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::source() const
0146 {
0147     return m_d->source;
0148 }
0149 
0150 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::overlay(int index) const
0151 {
0152     return !m_d->overlays.isEmpty() ? m_d->overlays[index] : m_d->source;
0153 }
0154 
0155 void KisOverlayPaintDeviceWrapper::readRect(const QRect &rc)
0156 {
0157     readRects({rc});
0158 }
0159 
0160 void KisOverlayPaintDeviceWrapper::writeRect(const QRect &rc, int index)
0161 {
0162     writeRects({rc}, index);
0163 }
0164 
0165 void KisOverlayPaintDeviceWrapper::readRects(const QVector<QRect> &rects)
0166 {
0167     if (rects.isEmpty()) return;
0168     if (m_d->overlays.isEmpty()) return;
0169 
0170     QRect cropRect = m_d->source->extent();
0171     QVector<QRect> rectsToRead;
0172 
0173     Q_FOREACH (const QRect &rc, rects) {
0174         if (m_d->source->defaultBounds()->wrapAroundMode()) {
0175             const QRect wrapRect = m_d->source->defaultBounds()->imageBorderRect();
0176             KisWrappedRect wrappedRect(rc, wrapRect);
0177             Q_FOREACH (const QRect &wrc, wrappedRect) {
0178                 rectsToRead += m_d->grid.addRect(wrc);
0179             }
0180             cropRect &= wrapRect;
0181         } else {
0182             rectsToRead += m_d->grid.addRect(rc);
0183         }
0184     }
0185 
0186     KisRegion::makeGridLikeRectsUnique(rectsToRead);
0187 
0188     //TODO: implement synchronization of the offset between the grid and devices
0189 
0190     if (!m_d->scaler) {
0191         Q_FOREACH (KisPaintDeviceSP overlay, m_d->overlays) {
0192             Q_FOREACH (const QRect &rect, rectsToRead) {
0193                 const QRect croppedRect = rect & cropRect;
0194                 if (croppedRect.isEmpty()) continue;
0195 
0196                 KisPainter::copyAreaOptimized(croppedRect.topLeft(), m_d->source, overlay, croppedRect);
0197             }
0198         }
0199     } else {
0200         KisPaintDeviceSP overlay = m_d->overlays.first();
0201 
0202         KisRandomConstAccessorSP srcIt = m_d->source->createRandomConstAccessorNG();
0203         KisRandomAccessorSP dstIt = overlay->createRandomAccessorNG();
0204 
0205         auto rectIter = rectsToRead.begin();
0206         while (rectIter != rectsToRead.end()) {
0207             const QRect croppedRect = *rectIter & cropRect;
0208 
0209             if (!croppedRect.isEmpty()) {
0210 
0211                 KritaUtils::processTwoDevicesWithStrides(croppedRect,
0212                                                          srcIt, dstIt,
0213                     [this] (const quint8 *src, int srcRowStride,
0214                             quint8 *dst, int dstRowStride,
0215                             int numRows, int numColumns) {
0216 
0217                     m_d->scaler->convertU8ToU16(src, srcRowStride,
0218                                                 dst, dstRowStride,
0219                                                 numRows, numColumns);
0220                 });
0221 
0222                 for (auto it = std::next(m_d->overlays.begin()); it != m_d->overlays.end(); ++it) {
0223                     KisPaintDeviceSP otherOverlay = *it;
0224                     KisPainter::copyAreaOptimized(croppedRect.topLeft(), overlay, otherOverlay, croppedRect);
0225                 }
0226             }
0227 
0228             rectIter++;
0229         }
0230     }
0231 }
0232 
0233 void KisOverlayPaintDeviceWrapper::writeRects(const QVector<QRect> &rects, int index)
0234 {
0235     if (rects.isEmpty()) return;
0236     if (m_d->overlays.isEmpty()) return;
0237 
0238     KisPaintDeviceSP destinationDevice =
0239         m_d->externalDestination ? m_d->externalDestination : m_d->source;
0240 
0241     if (!m_d->scaler ||
0242         (destinationDevice != m_d->source &&
0243          *destinationDevice->colorSpace() != *m_d->source->colorSpace())) {
0244 
0245         Q_FOREACH (const QRect &rc, rects) {
0246             KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->grid.contains(rc));
0247             KisPainter::copyAreaOptimized(rc.topLeft(), m_d->overlays[index], destinationDevice, rc);
0248         }
0249     } else {
0250         KisPaintDeviceSP overlay = m_d->overlays[index];
0251 
0252 
0253         KisRandomConstAccessorSP srcIt = overlay->createRandomConstAccessorNG();
0254         KisRandomAccessorSP dstIt = destinationDevice->createRandomAccessorNG();
0255 
0256         Q_FOREACH (const QRect &rc, rects) {
0257             KritaUtils::processTwoDevicesWithStrides(rc,
0258                                                      srcIt, dstIt,
0259                 [this] (const quint8 *src, int srcRowStride,
0260                         quint8 *dst, int dstRowStride,
0261                         int numRows, int numColumns) {
0262 
0263                 m_d->scaler->convertU16ToU8(src, srcRowStride,
0264                                             dst, dstRowStride,
0265                                             numRows, numColumns);
0266             });
0267         }
0268     }
0269 }
0270 
0271 const KoColorSpace *KisOverlayPaintDeviceWrapper::overlayColorSpace() const
0272 {
0273     return !m_d->overlays.isEmpty() ? m_d->overlays.first()->colorSpace() : m_d->source->colorSpace();
0274 }
0275 
0276 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::createPreciseCompositionSourceDevice()
0277 {
0278     /**
0279      * TODO: this funciton has rather vague meaning when forcedOverlayColorSpace
0280      *       feature is used
0281      */
0282 
0283     KisPaintDeviceSP result;
0284 
0285     if (!m_d->usePreciseMode) {
0286         result = source()->createCompositionSourceDevice();
0287     } else {
0288         const KoColorSpace *compositionColorSpace =
0289             m_d->source->compositionSourceColorSpace();
0290 
0291         const KoColorSpace *preciseCompositionColorSpace =
0292                 KoColorSpaceRegistry::instance()->colorSpace(
0293                     compositionColorSpace->colorModelId().id(),
0294                     Integer16BitsColorDepthID.id(),
0295                     compositionColorSpace->profile());
0296 
0297         KisPaintDeviceSP device = new KisPaintDevice(preciseCompositionColorSpace);
0298         device->setDefaultBounds(m_d->source->defaultBounds());
0299         result = device;
0300     }
0301 
0302     return result;
0303 }
0304 
0305 void KisOverlayPaintDeviceWrapper::beginTransaction(KUndo2Command *parent)
0306 {
0307     KIS_SAFE_ASSERT_RECOVER(!m_d->rootTransactionData) {
0308         m_d->rootTransactionData.reset();
0309     }
0310 
0311     if (!m_d->previousGrid) {
0312         m_d->previousGrid.reset(new KisRectsGrid(m_d->grid));
0313     }
0314 
0315     m_d->rootTransactionData.reset(new KUndo2Command(parent));
0316 
0317     m_d->changeOverlayCommand = new KisChangeOverlayWrapperCommand(m_d.data());
0318     (void) new KisCommandUtils::SkipFirstRedoWrapper(m_d->changeOverlayCommand, m_d->rootTransactionData.data());
0319     m_d->changeOverlayCommand->m_oldRectsGrid = m_d->previousGrid;
0320 
0321     for (const auto &overlayDevice : m_d->overlays) {
0322         m_d->overlayTransactions.emplace_back(new KisTransaction(overlayDevice, m_d->rootTransactionData.data()));
0323     }
0324 }
0325 
0326 KUndo2Command *KisOverlayPaintDeviceWrapper::endTransaction()
0327 {
0328     KUndo2Command *result = nullptr;
0329 
0330     KIS_SAFE_ASSERT_RECOVER(m_d->rootTransactionData) {
0331         m_d->overlayTransactions.clear();
0332         return result;
0333     }
0334 
0335     m_d->previousGrid.reset(new KisRectsGrid(m_d->grid));
0336 
0337     m_d->changeOverlayCommand->m_newRectsGrid = m_d->previousGrid;
0338     result = m_d->rootTransactionData.take();
0339 
0340     for (auto &transactionPtr : m_d->overlayTransactions) {
0341         // the transactions are assigned as children to m_d->changeOverlayCommand
0342         (void) transactionPtr->endAndTake();
0343     }
0344     m_d->overlayTransactions.clear();
0345 
0346     return result;
0347 }