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

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_onion_skin_compositor.h"
0008 
0009 #include "kis_paint_device.h"
0010 #include "kis_painter.h"
0011 #include "KoColor.h"
0012 #include "KoColorSpace.h"
0013 #include "KoCompositeOpRegistry.h"
0014 #include "KoColorSpaceConstants.h"
0015 #include "kis_image_config.h"
0016 #include "kis_raster_keyframe_channel.h"
0017 
0018 Q_GLOBAL_STATIC(KisOnionSkinCompositor, s_instance)
0019 
0020 struct KisOnionSkinCompositor::Private
0021 {
0022     int numberOfSkins = 0;
0023     int tintFactor = 0;
0024     QColor backwardTintColor;
0025     QColor forwardTintColor;
0026     QVector<int> backwardOpacities;
0027     QVector<int> forwardOpacities;
0028     int configSeqNo = 0;
0029     QSet<int> colorLabelFilter;
0030 
0031     int skinOpacity(int offset)
0032     {
0033         const QVector<int> &bo = backwardOpacities;
0034         const QVector<int> &fo = forwardOpacities;
0035 
0036         return offset > 0 ? fo[qAbs(offset) - 1] : bo[qAbs(offset) - 1];
0037     }
0038 
0039     KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace)
0040     {
0041         KisPaintDeviceSP tintDevice = new KisPaintDevice(colorSpace);
0042         KoColor color = KoColor(tintColor, colorSpace);
0043         tintDevice->setDefaultPixel(color);
0044         return tintDevice;
0045     }
0046 
0047 
0048     KisRasterKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, int &outFrame, bool backwards) // TODO: Double-check this function... outFrame might be weird?
0049     {
0050         while (!channel->keyframeAt(outFrame).isNull()) {
0051             outFrame = backwards ? channel->previousKeyframeTime(outFrame) : channel->nextKeyframeTime(outFrame);
0052             if (colorLabelFilter.isEmpty()) {
0053                 return channel->keyframeAt<KisRasterKeyframe>(outFrame);
0054             } else if (channel->keyframeAt<KisRasterKeyframe>(outFrame)) {
0055                 if (colorLabelFilter.contains(channel->keyframeAt(outFrame)->colorLabel())) {
0056                     return channel->keyframeAt<KisRasterKeyframe>(outFrame);
0057                 }
0058             }
0059         }
0060         return channel->keyframeAt<KisRasterKeyframe>(outFrame);
0061     }
0062 
0063     void tryCompositeFrame(KisRasterKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect)
0064     {
0065         if (keyframe.isNull() || opacity == OPACITY_TRANSPARENT_U8) return;
0066 
0067         keyframe->writeFrameToDevice(gcFrame.device());
0068 
0069         gcFrame.bitBlt(rect.topLeft(), tintSource, rect);
0070 
0071         gcDest.setOpacity(opacity);
0072         gcDest.bitBlt(rect.topLeft(), gcFrame.device(), rect);
0073     }
0074 
0075     void refreshConfig()
0076     {
0077         KisImageConfig config(true);
0078 
0079         numberOfSkins = config.numberOfOnionSkins();
0080         tintFactor = config.onionSkinTintFactor();
0081         backwardTintColor = config.onionSkinTintColorBackward();
0082         forwardTintColor = config.onionSkinTintColorForward();
0083 
0084         backwardOpacities.resize(numberOfSkins);
0085         forwardOpacities.resize(numberOfSkins);
0086 
0087         const int mainState = (int) config.onionSkinState(0);
0088         const qreal scaleFactor = mainState * config.onionSkinOpacity(0) / 255.0;
0089 
0090         for (int i = 0; i < numberOfSkins; i++) {
0091             int backwardState = (int) config.onionSkinState(-(i + 1));
0092             int forwardState = (int) config.onionSkinState(i + 1);
0093 
0094             backwardOpacities[i] = scaleFactor * backwardState * config.onionSkinOpacity(-(i + 1));
0095             forwardOpacities[i] = scaleFactor * forwardState * config.onionSkinOpacity(i + 1);
0096         }
0097 
0098         configSeqNo++;
0099     }
0100 
0101     QRect updateExtentOnFrameChange(KisRasterKeyframeChannel *channel,
0102                                     int prevActiveTime, int prevIgnoredTime,
0103                                     int nowActiveTime, int nowIgnoredTime);
0104 
0105 };
0106 
0107 KisOnionSkinCompositor *KisOnionSkinCompositor::instance()
0108 {
0109     return s_instance;
0110 }
0111 
0112 KisOnionSkinCompositor::KisOnionSkinCompositor()
0113     : m_d(new Private)
0114 {
0115     m_d->refreshConfig();
0116 }
0117 
0118 KisOnionSkinCompositor::~KisOnionSkinCompositor()
0119 {}
0120 
0121 int KisOnionSkinCompositor::configSeqNo() const
0122 {
0123     return m_d->configSeqNo;
0124 }
0125 
0126 void KisOnionSkinCompositor::setColorLabelFilter(QSet<int> colors)
0127 {
0128     m_d->colorLabelFilter = colors;
0129 }
0130 
0131 QSet<int> KisOnionSkinCompositor::colorLabelFilter()
0132 {
0133     return m_d->colorLabelFilter;
0134 }
0135 
0136 void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect)
0137 {
0138     KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel();
0139 
0140     KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace());
0141     KisPainter gcFrame(frameDevice);
0142     QBitArray channelFlags = sourceDevice->colorSpace()->channelFlags(true, false);
0143     gcFrame.setChannelFlags(channelFlags);
0144     gcFrame.setOpacity(m_d->tintFactor);
0145 
0146     KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace());
0147     KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace());
0148 
0149     KisPainter gcDest(targetDevice);
0150     gcDest.setCompositeOpId(sourceDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND));
0151 
0152     int keyframeTimeBck;
0153     int keyframeTimeFwd;
0154 
0155     int time = sourceDevice->defaultBounds()->currentTime();
0156 
0157     if (!keyframes) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
0158         return;
0159     }
0160 
0161     keyframeTimeBck = keyframeTimeFwd = keyframes->activeKeyframeTime(time);
0162 
0163     for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
0164         KisRasterKeyframeSP backKeyframe = m_d->getNextFrameToComposite(keyframes, keyframeTimeBck, true);
0165         KisRasterKeyframeSP forwardKeyframe = m_d->getNextFrameToComposite(keyframes, keyframeTimeFwd, false);
0166 
0167         if (!backKeyframe.isNull()) {
0168             m_d->tryCompositeFrame(backKeyframe, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect);
0169         }
0170 
0171         if (!forwardKeyframe.isNull()) {
0172             m_d->tryCompositeFrame(forwardKeyframe, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect);
0173         }
0174     }
0175 
0176 }
0177 
0178 QRect KisOnionSkinCompositor::calculateFullExtent(const KisPaintDeviceSP device)
0179 {
0180     QRect rect;
0181 
0182     KisRasterKeyframeChannel *channel = device->keyframeChannel();
0183     if (!channel) return rect;
0184 
0185     int currentKeyTime = channel->firstKeyframeTime();
0186 
0187     while (channel->keyframeAt(currentKeyTime)) {
0188         rect |= channel->frameExtents(channel->keyframeAt(currentKeyTime));
0189         currentKeyTime = channel->nextKeyframeTime(currentKeyTime);
0190     }
0191 
0192     return rect;
0193 }
0194 
0195 QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device, int time)
0196 {
0197     QRect rect;
0198     int keyframeTimeBack;
0199     int keyframeTimeFwd;
0200 
0201     KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
0202 
0203     if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
0204         return rect;
0205     }
0206 
0207     keyframeTimeBack = keyframeTimeFwd = time;
0208 
0209     for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
0210         if (channel->keyframeAt(keyframeTimeBack)) {
0211             keyframeTimeBack = channel->previousKeyframeTime(keyframeTimeBack);
0212 
0213             if (channel->keyframeAt(keyframeTimeBack)) {
0214                 rect |= channel->frameExtents(channel->keyframeAt(keyframeTimeBack));
0215             }
0216         }
0217 
0218         if (channel->keyframeAt(keyframeTimeFwd)) {
0219             keyframeTimeFwd = channel->nextKeyframeTime(keyframeTimeFwd);
0220 
0221             if (channel->keyframeAt(keyframeTimeFwd)) {
0222                 rect |= channel->frameExtents(channel->keyframeAt(keyframeTimeFwd));
0223             }
0224         }
0225     }
0226 
0227     return rect;
0228 }
0229 
0230 QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device)
0231 {
0232     KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
0233 
0234     if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
0235         return QRect();
0236     }
0237 
0238     return calculateExtent(device, channel->activeKeyframeTime());
0239 }
0240 
0241 
0242 QRect KisOnionSkinCompositor::Private::updateExtentOnFrameChange(KisRasterKeyframeChannel *channel,
0243                                                                  int prevActiveTime, int prevIgnoredTime,
0244                                                                  int nowActiveTime, int nowIgnoredTime)
0245 {
0246     QRect rect;
0247 
0248     std::vector<int> skinsBefore;
0249     std::vector<int> skinsAfter;
0250 
0251     auto fetchSkins = [this] (KisRasterKeyframeChannel *channel, const int activeTime, int ignoredFrame) {
0252         std::vector<int> skinsLeft;
0253         std::vector<int> skinsRight;
0254 
0255         int keyframeTimeBck = activeTime;
0256         int keyframeTimeFwd = activeTime;
0257 
0258         auto addNextFrame = [channel, this, ignoredFrame] (int offset, int &startTime, std::vector<int> &skins, bool backwards) {
0259             KisRasterKeyframeSP keyframe;
0260             do {
0261                 keyframe = getNextFrameToComposite(channel, startTime, backwards);
0262             } while (keyframe && startTime == ignoredFrame);
0263 
0264             if (keyframe && skinOpacity(-offset) != OPACITY_TRANSPARENT_U8) {
0265                 skins.push_back(startTime);
0266             }
0267         };
0268 
0269         for (int offset = 1; offset <= numberOfSkins; offset++) {
0270             addNextFrame(offset, keyframeTimeBck, skinsLeft, true);
0271             addNextFrame(offset, keyframeTimeFwd, skinsRight, false);
0272         }
0273 
0274         std::reverse(skinsLeft.begin(), skinsLeft.end());
0275         skinsLeft.reserve(skinsLeft.size() + skinsRight.size());
0276         std::copy(skinsRight.begin(), skinsRight.end(), std::back_inserter(skinsLeft));
0277 
0278         return skinsLeft;
0279     };
0280 
0281     skinsBefore = fetchSkins(channel, prevActiveTime, prevIgnoredTime);
0282     skinsAfter = fetchSkins(channel, nowActiveTime, nowIgnoredTime);
0283 
0284     std::vector<int> changedSkins;
0285 
0286     std::set_symmetric_difference(skinsBefore.begin(), skinsBefore.end(),
0287                                   skinsAfter.begin(), skinsAfter.end(),
0288                                   std::back_inserter(changedSkins));
0289 
0290 //    ENTER_FUNCTION() << ppVar(skinsBefore);
0291 //    ENTER_FUNCTION() << ppVar(skinsAfter);
0292 //    ENTER_FUNCTION() << ppVar(changedSkins);
0293 
0294     for (auto it = changedSkins.begin(); it != changedSkins.end(); ++it) {
0295         KIS_SAFE_ASSERT_RECOVER(channel->keyframeAt(*it)) { continue; }
0296         rect |= channel->frameExtents(channel->keyframeAt(*it));
0297     }
0298 
0299     return rect;
0300 }
0301 
0302 QRect KisOnionSkinCompositor::updateExtentOnAddition(const KisPaintDeviceSP device, int addedTime)
0303 {
0304     QRect rect;
0305 
0306     KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
0307 
0308     if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
0309         return rect;
0310     }
0311 
0312     int currentActiveTime = channel->activeKeyframeTime();
0313     int prevActiveTime = -1;
0314 
0315     if (currentActiveTime == addedTime) {
0316         prevActiveTime = channel->previousKeyframeTime(currentActiveTime);
0317     } else {
0318         prevActiveTime = currentActiveTime;
0319     }
0320 
0321     rect = m_d->updateExtentOnFrameChange(channel,
0322                                           prevActiveTime, addedTime,
0323                                           currentActiveTime, -1);
0324 
0325     return rect;
0326 }
0327 
0328 void KisOnionSkinCompositor::configChanged()
0329 {
0330     m_d->refreshConfig();
0331     emit sigOnionSkinChanged();
0332 }