File indexing completed on 2024-05-19 04:26:43

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->setSupportsWraparoundMode(source->supportsWraproundMode());
0126         overlay->moveTo(source->offset());
0127 
0128         m_d->overlays.append(overlay);
0129     }
0130 }
0131 
0132 KisOverlayPaintDeviceWrapper::~KisOverlayPaintDeviceWrapper()
0133 {
0134 }
0135 
0136 void KisOverlayPaintDeviceWrapper::setExternalDestination(KisPaintDeviceSP device)
0137 {
0138     m_d->externalDestination = device;
0139 }
0140 
0141 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::externalDestination() const
0142 {
0143     return m_d->externalDestination;
0144 }
0145 
0146 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::source() const
0147 {
0148     return m_d->source;
0149 }
0150 
0151 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::overlay(int index) const
0152 {
0153     return !m_d->overlays.isEmpty() ? m_d->overlays[index] : m_d->source;
0154 }
0155 
0156 void KisOverlayPaintDeviceWrapper::readRect(const QRect &rc)
0157 {
0158     readRects({rc});
0159 }
0160 
0161 void KisOverlayPaintDeviceWrapper::writeRect(const QRect &rc, int index)
0162 {
0163     writeRects({rc}, index);
0164 }
0165 
0166 void KisOverlayPaintDeviceWrapper::readRects(const QVector<QRect> &rects)
0167 {
0168     if (rects.isEmpty()) return;
0169     if (m_d->overlays.isEmpty()) return;
0170 
0171     QRect cropRect = m_d->source->extent();
0172     QVector<QRect> rectsToRead;
0173 
0174     Q_FOREACH (const QRect &rc, rects) {
0175         if (m_d->source->defaultBounds()->wrapAroundMode()) {
0176             const QRect wrapRect = m_d->source->defaultBounds()->imageBorderRect();
0177             KisWrappedRect wrappedRect(rc, wrapRect, m_d->source->defaultBounds()->wrapAroundModeAxis());
0178             Q_FOREACH (const QRect &wrc, wrappedRect) {
0179                 rectsToRead += m_d->grid.addRect(wrc);
0180             }
0181             cropRect &= wrapRect;
0182         } else {
0183             rectsToRead += m_d->grid.addRect(rc);
0184         }
0185     }
0186 
0187     KisRegion::makeGridLikeRectsUnique(rectsToRead);
0188 
0189     //TODO: implement synchronization of the offset between the grid and devices
0190 
0191     if (!m_d->scaler) {
0192         Q_FOREACH (KisPaintDeviceSP overlay, m_d->overlays) {
0193             Q_FOREACH (const QRect &rect, rectsToRead) {
0194                 const QRect croppedRect = rect & cropRect;
0195                 if (croppedRect.isEmpty()) continue;
0196 
0197                 KisPainter::copyAreaOptimized(croppedRect.topLeft(), m_d->source, overlay, croppedRect);
0198             }
0199         }
0200     } else {
0201         KisPaintDeviceSP overlay = m_d->overlays.first();
0202 
0203         KisRandomConstAccessorSP srcIt = m_d->source->createRandomConstAccessorNG();
0204         KisRandomAccessorSP dstIt = overlay->createRandomAccessorNG();
0205 
0206         auto rectIter = rectsToRead.begin();
0207         while (rectIter != rectsToRead.end()) {
0208             const QRect croppedRect = *rectIter & cropRect;
0209 
0210             if (!croppedRect.isEmpty()) {
0211 
0212                 KritaUtils::processTwoDevicesWithStrides(croppedRect,
0213                                                          srcIt, dstIt,
0214                     [this] (const quint8 *src, int srcRowStride,
0215                             quint8 *dst, int dstRowStride,
0216                             int numRows, int numColumns) {
0217 
0218                     m_d->scaler->convertU8ToU16(src, srcRowStride,
0219                                                 dst, dstRowStride,
0220                                                 numRows, numColumns);
0221                 });
0222 
0223                 for (auto it = std::next(m_d->overlays.begin()); it != m_d->overlays.end(); ++it) {
0224                     KisPaintDeviceSP otherOverlay = *it;
0225                     KisPainter::copyAreaOptimized(croppedRect.topLeft(), overlay, otherOverlay, croppedRect);
0226                 }
0227             }
0228 
0229             rectIter++;
0230         }
0231     }
0232 }
0233 
0234 void KisOverlayPaintDeviceWrapper::writeRects(const QVector<QRect> &rects, int index)
0235 {
0236     if (rects.isEmpty()) return;
0237     if (m_d->overlays.isEmpty()) return;
0238 
0239     KisPaintDeviceSP destinationDevice =
0240         m_d->externalDestination ? m_d->externalDestination : m_d->source;
0241 
0242     if (!m_d->scaler ||
0243         (destinationDevice != m_d->source &&
0244          *destinationDevice->colorSpace() != *m_d->source->colorSpace())) {
0245 
0246         Q_FOREACH (const QRect &rc, rects) {
0247             KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->grid.contains(rc));
0248             KisPainter::copyAreaOptimized(rc.topLeft(), m_d->overlays[index], destinationDevice, rc);
0249         }
0250     } else {
0251         KisPaintDeviceSP overlay = m_d->overlays[index];
0252 
0253 
0254         KisRandomConstAccessorSP srcIt = overlay->createRandomConstAccessorNG();
0255         KisRandomAccessorSP dstIt = destinationDevice->createRandomAccessorNG();
0256 
0257         Q_FOREACH (const QRect &rc, rects) {
0258             KritaUtils::processTwoDevicesWithStrides(rc,
0259                                                      srcIt, dstIt,
0260                 [this] (const quint8 *src, int srcRowStride,
0261                         quint8 *dst, int dstRowStride,
0262                         int numRows, int numColumns) {
0263 
0264                 m_d->scaler->convertU16ToU8(src, srcRowStride,
0265                                             dst, dstRowStride,
0266                                             numRows, numColumns);
0267             });
0268         }
0269     }
0270 }
0271 
0272 const KoColorSpace *KisOverlayPaintDeviceWrapper::overlayColorSpace() const
0273 {
0274     return !m_d->overlays.isEmpty() ? m_d->overlays.first()->colorSpace() : m_d->source->colorSpace();
0275 }
0276 
0277 KisPaintDeviceSP KisOverlayPaintDeviceWrapper::createPreciseCompositionSourceDevice()
0278 {
0279     /**
0280      * TODO: this function has rather vague meaning when forcedOverlayColorSpace
0281      *       feature is used
0282      */
0283 
0284     KisPaintDeviceSP result;
0285 
0286     if (!m_d->usePreciseMode) {
0287         result = source()->createCompositionSourceDevice();
0288     } else {
0289         const KoColorSpace *compositionColorSpace =
0290             m_d->source->compositionSourceColorSpace();
0291 
0292         const KoColorSpace *preciseCompositionColorSpace =
0293                 KoColorSpaceRegistry::instance()->colorSpace(
0294                     compositionColorSpace->colorModelId().id(),
0295                     Integer16BitsColorDepthID.id(),
0296                     compositionColorSpace->profile());
0297 
0298         KisPaintDeviceSP device = new KisPaintDevice(preciseCompositionColorSpace);
0299         device->setDefaultBounds(m_d->source->defaultBounds());
0300         result = device;
0301     }
0302 
0303     return result;
0304 }
0305 
0306 void KisOverlayPaintDeviceWrapper::beginTransaction(KUndo2Command *parent)
0307 {
0308     KIS_SAFE_ASSERT_RECOVER(!m_d->rootTransactionData) {
0309         m_d->rootTransactionData.reset();
0310     }
0311 
0312     if (!m_d->previousGrid) {
0313         m_d->previousGrid.reset(new KisRectsGrid(m_d->grid));
0314     }
0315 
0316     m_d->rootTransactionData.reset(new KUndo2Command(parent));
0317 
0318     m_d->changeOverlayCommand = new KisChangeOverlayWrapperCommand(m_d.data());
0319     (void) new KisCommandUtils::SkipFirstRedoWrapper(m_d->changeOverlayCommand, m_d->rootTransactionData.data());
0320     m_d->changeOverlayCommand->m_oldRectsGrid = m_d->previousGrid;
0321 
0322     for (const auto &overlayDevice : m_d->overlays) {
0323         m_d->overlayTransactions.emplace_back(new KisTransaction(overlayDevice, m_d->rootTransactionData.data()));
0324     }
0325 }
0326 
0327 KUndo2Command *KisOverlayPaintDeviceWrapper::endTransaction()
0328 {
0329     KUndo2Command *result = nullptr;
0330 
0331     KIS_SAFE_ASSERT_RECOVER(m_d->rootTransactionData) {
0332         m_d->overlayTransactions.clear();
0333         return result;
0334     }
0335 
0336     m_d->previousGrid.reset(new KisRectsGrid(m_d->grid));
0337 
0338     m_d->changeOverlayCommand->m_newRectsGrid = m_d->previousGrid;
0339     result = m_d->rootTransactionData.take();
0340 
0341     for (auto &transactionPtr : m_d->overlayTransactions) {
0342         // the transactions are assigned as children to m_d->changeOverlayCommand
0343         (void) transactionPtr->endAndTake();
0344     }
0345     m_d->overlayTransactions.clear();
0346 
0347     return result;
0348 }