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

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 
0102 KisOnionSkinCompositor *KisOnionSkinCompositor::instance()
0103 {
0104     return s_instance;
0105 }
0106 
0107 KisOnionSkinCompositor::KisOnionSkinCompositor()
0108     : m_d(new Private)
0109 {
0110     m_d->refreshConfig();
0111 }
0112 
0113 KisOnionSkinCompositor::~KisOnionSkinCompositor()
0114 {}
0115 
0116 int KisOnionSkinCompositor::configSeqNo() const
0117 {
0118     return m_d->configSeqNo;
0119 }
0120 
0121 void KisOnionSkinCompositor::setColorLabelFilter(QSet<int> colors)
0122 {
0123     m_d->colorLabelFilter = colors;
0124 }
0125 
0126 QSet<int> KisOnionSkinCompositor::colorLabelFilter()
0127 {
0128     return m_d->colorLabelFilter;
0129 }
0130 
0131 void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect)
0132 {
0133     KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel();
0134 
0135     KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace());
0136     KisPainter gcFrame(frameDevice);
0137     QBitArray channelFlags = targetDevice->colorSpace()->channelFlags(true, false);
0138     gcFrame.setChannelFlags(channelFlags);
0139     gcFrame.setOpacity(m_d->tintFactor);
0140 
0141     KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace());
0142     KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace());
0143 
0144     KisPainter gcDest(targetDevice);
0145     gcDest.setCompositeOpId(sourceDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND));
0146 
0147     int keyframeTimeBck;
0148     int keyframeTimeFwd;
0149 
0150     int time = sourceDevice->defaultBounds()->currentTime();
0151 
0152     if (!keyframes) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
0153         return;
0154     }
0155 
0156     keyframeTimeBck = keyframeTimeFwd = keyframes->activeKeyframeTime(time);
0157 
0158     for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
0159         KisRasterKeyframeSP backKeyframe = m_d->getNextFrameToComposite(keyframes, keyframeTimeBck, true);
0160         KisRasterKeyframeSP forwardKeyframe = m_d->getNextFrameToComposite(keyframes, keyframeTimeFwd, false);
0161 
0162         if (!backKeyframe.isNull()) {
0163             m_d->tryCompositeFrame(backKeyframe, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect);
0164         }
0165 
0166         if (!forwardKeyframe.isNull()) {
0167             m_d->tryCompositeFrame(forwardKeyframe, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect);
0168         }
0169     }
0170 
0171 }
0172 
0173 QRect KisOnionSkinCompositor::calculateFullExtent(const KisPaintDeviceSP device)
0174 {
0175     QRect rect;
0176 
0177     KisRasterKeyframeChannel *channel = device->keyframeChannel();
0178     if (!channel) return rect;
0179 
0180     int currentKeyTime = channel->firstKeyframeTime();
0181 
0182     while (channel->keyframeAt(currentKeyTime)) {
0183         rect |= channel->frameExtents(channel->keyframeAt(currentKeyTime));
0184         currentKeyTime = channel->nextKeyframeTime(currentKeyTime);
0185     }
0186 
0187     return rect;
0188 }
0189 
0190 QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device)
0191 {
0192     QRect rect;
0193     int keyframeTimeBack;
0194     int keyframeTimeFwd;
0195 
0196     KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
0197 
0198     if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
0199         return rect;
0200     }
0201 
0202     keyframeTimeBack = keyframeTimeFwd = channel->activeKeyframeTime();
0203 
0204     for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
0205         if (channel->keyframeAt(keyframeTimeBack)) {
0206             keyframeTimeBack = channel->previousKeyframeTime(keyframeTimeBack);
0207 
0208             if (channel->keyframeAt(keyframeTimeBack)) {
0209                 rect |= channel->frameExtents(channel->keyframeAt(keyframeTimeBack));
0210             }
0211         }
0212 
0213         if (channel->keyframeAt(keyframeTimeFwd)) {
0214             keyframeTimeFwd = channel->nextKeyframeTime(keyframeTimeFwd);
0215 
0216             if (channel->keyframeAt(keyframeTimeFwd)) {
0217                 rect |= channel->frameExtents(channel->keyframeAt(keyframeTimeFwd));
0218             }
0219         }
0220     }
0221 
0222     return rect;
0223 }
0224 
0225 void KisOnionSkinCompositor::configChanged()
0226 {
0227     m_d->refreshConfig();
0228     emit sigOnionSkinChanged();
0229 }