Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_onion_skin_compositor.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include "kis_paint_device.h"
10#include "kis_painter.h"
11#include "KoColor.h"
12#include "KoColorSpace.h"
15#include "kis_image_config.h"
17
19
21{
22 int numberOfSkins = 0;
23 int tintFactor = 0;
28 int configSeqNo = 0;
30
31 int skinOpacity(int offset)
32 {
33 const QVector<int> &bo = backwardOpacities;
34 const QVector<int> &fo = forwardOpacities;
35
36 return offset > 0 ? fo[qAbs(offset) - 1] : bo[qAbs(offset) - 1];
37 }
38
39 KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace)
40 {
41 KisPaintDeviceSP tintDevice = new KisPaintDevice(colorSpace);
42 KoColor color = KoColor(tintColor, colorSpace);
43 tintDevice->setDefaultPixel(color);
44 return tintDevice;
45 }
46
47
48 KisRasterKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, int &outFrame, bool backwards) // TODO: Double-check this function... outFrame might be weird?
49 {
50 while (!channel->keyframeAt(outFrame).isNull()) {
51 outFrame = backwards ? channel->previousKeyframeTime(outFrame) : channel->nextKeyframeTime(outFrame);
52 if (colorLabelFilter.isEmpty()) {
53 return channel->keyframeAt<KisRasterKeyframe>(outFrame);
54 } else if (channel->keyframeAt<KisRasterKeyframe>(outFrame)) {
55 if (colorLabelFilter.contains(channel->keyframeAt(outFrame)->colorLabel())) {
56 return channel->keyframeAt<KisRasterKeyframe>(outFrame);
57 }
58 }
59 }
60 return channel->keyframeAt<KisRasterKeyframe>(outFrame);
61 }
62
63 void tryCompositeFrame(KisRasterKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect)
64 {
65 if (keyframe.isNull() || opacity == OPACITY_TRANSPARENT_U8) return;
66
67 keyframe->writeFrameToDevice(gcFrame.device());
68
69 gcFrame.bitBlt(rect.topLeft(), tintSource, rect);
70
71 gcDest.setOpacityU8(opacity);
72 gcDest.bitBlt(rect.topLeft(), gcFrame.device(), rect);
73 }
74
76 {
77 KisImageConfig config(true);
78
79 numberOfSkins = config.numberOfOnionSkins();
80 tintFactor = config.onionSkinTintFactor();
81 backwardTintColor = config.onionSkinTintColorBackward();
82 forwardTintColor = config.onionSkinTintColorForward();
83
84 backwardOpacities.resize(numberOfSkins);
85 forwardOpacities.resize(numberOfSkins);
86
87 const int mainState = (int) config.onionSkinState(0);
88 const qreal scaleFactor = mainState * config.onionSkinOpacity(0) / 255.0;
89
90 for (int i = 0; i < numberOfSkins; i++) {
91 int backwardState = (int) config.onionSkinState(-(i + 1));
92 int forwardState = (int) config.onionSkinState(i + 1);
93
94 backwardOpacities[i] = scaleFactor * backwardState * config.onionSkinOpacity(-(i + 1));
95 forwardOpacities[i] = scaleFactor * forwardState * config.onionSkinOpacity(i + 1);
96 }
97
98 configSeqNo++;
99 }
100
101 QRect updateExtentOnFrameChange(KisRasterKeyframeChannel *channel,
102 int prevActiveTime, int prevIgnoredTime,
103 int nowActiveTime, int nowIgnoredTime);
104
105};
106
111
113 : m_d(new Private)
114{
115 m_d->refreshConfig();
116}
117
120
122{
123 return m_d->configSeqNo;
124}
125
127{
128 m_d->colorLabelFilter = colors;
129}
130
132{
133 return m_d->colorLabelFilter;
134}
135
136void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect)
137{
138 KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel();
139
140 KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace());
141 KisPainter gcFrame(frameDevice);
142 QBitArray channelFlags = sourceDevice->colorSpace()->channelFlags(true, false);
143 gcFrame.setChannelFlags(channelFlags);
144 gcFrame.setOpacityU8(m_d->tintFactor);
145
146 KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace());
147 KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace());
148
149 KisPainter gcDest(targetDevice);
151
152 int keyframeTimeBck;
153 int keyframeTimeFwd;
154
155 int time = sourceDevice->defaultBounds()->currentTime();
156
157 if (!keyframes) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
158 return;
159 }
160
161 keyframeTimeBck = keyframeTimeFwd = keyframes->activeKeyframeTime(time);
162
163 for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
164 KisRasterKeyframeSP backKeyframe = m_d->getNextFrameToComposite(keyframes, keyframeTimeBck, true);
165 KisRasterKeyframeSP forwardKeyframe = m_d->getNextFrameToComposite(keyframes, keyframeTimeFwd, false);
166
167 if (!backKeyframe.isNull()) {
168 m_d->tryCompositeFrame(backKeyframe, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect);
169 }
170
171 if (!forwardKeyframe.isNull()) {
172 m_d->tryCompositeFrame(forwardKeyframe, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect);
173 }
174 }
175
176}
177
179{
180 QRect rect;
181
182 KisRasterKeyframeChannel *channel = device->keyframeChannel();
183 if (!channel) return rect;
184
185 int currentKeyTime = channel->firstKeyframeTime();
186
187 while (channel->keyframeAt(currentKeyTime)) {
188 rect |= channel->frameExtents(channel->keyframeAt(currentKeyTime));
189 currentKeyTime = channel->nextKeyframeTime(currentKeyTime);
190 }
191
192 return rect;
193}
194
196{
197 QRect rect;
198 int keyframeTimeBack;
199 int keyframeTimeFwd;
200
201 KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
202
203 if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
204 return rect;
205 }
206
207 keyframeTimeBack = keyframeTimeFwd = time;
208
209 for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
210 if (channel->keyframeAt(keyframeTimeBack)) {
211 keyframeTimeBack = channel->previousKeyframeTime(keyframeTimeBack);
212
213 if (channel->keyframeAt(keyframeTimeBack)) {
214 rect |= channel->frameExtents(channel->keyframeAt(keyframeTimeBack));
215 }
216 }
217
218 if (channel->keyframeAt(keyframeTimeFwd)) {
219 keyframeTimeFwd = channel->nextKeyframeTime(keyframeTimeFwd);
220
221 if (channel->keyframeAt(keyframeTimeFwd)) {
222 rect |= channel->frameExtents(channel->keyframeAt(keyframeTimeFwd));
223 }
224 }
225 }
226
227 return rect;
228}
229
231{
232 KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
233
234 if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
235 return QRect();
236 }
237
238 return calculateExtent(device, channel->activeKeyframeTime());
239}
240
241
243 int prevActiveTime, int prevIgnoredTime,
244 int nowActiveTime, int nowIgnoredTime)
245{
246 QRect rect;
247
248 std::vector<int> skinsBefore;
249 std::vector<int> skinsAfter;
250
251 auto fetchSkins = [this] (KisRasterKeyframeChannel *channel, const int activeTime, int ignoredFrame) {
252 std::vector<int> skinsLeft;
253 std::vector<int> skinsRight;
254
255 int keyframeTimeBck = activeTime;
256 int keyframeTimeFwd = activeTime;
257
258 auto addNextFrame = [channel, this, ignoredFrame] (int offset, int &startTime, std::vector<int> &skins, bool backwards) {
259 KisRasterKeyframeSP keyframe;
260 do {
261 keyframe = getNextFrameToComposite(channel, startTime, backwards);
262 } while (keyframe && startTime == ignoredFrame);
263
264 if (keyframe && skinOpacity(-offset) != OPACITY_TRANSPARENT_U8) {
265 skins.push_back(startTime);
266 }
267 };
268
269 for (int offset = 1; offset <= numberOfSkins; offset++) {
270 addNextFrame(offset, keyframeTimeBck, skinsLeft, true);
271 addNextFrame(offset, keyframeTimeFwd, skinsRight, false);
272 }
273
274 std::reverse(skinsLeft.begin(), skinsLeft.end());
275 skinsLeft.reserve(skinsLeft.size() + skinsRight.size());
276 std::copy(skinsRight.begin(), skinsRight.end(), std::back_inserter(skinsLeft));
277
278 return skinsLeft;
279 };
280
281 skinsBefore = fetchSkins(channel, prevActiveTime, prevIgnoredTime);
282 skinsAfter = fetchSkins(channel, nowActiveTime, nowIgnoredTime);
283
284 std::vector<int> changedSkins;
285
286 std::set_symmetric_difference(skinsBefore.begin(), skinsBefore.end(),
287 skinsAfter.begin(), skinsAfter.end(),
288 std::back_inserter(changedSkins));
289
290// ENTER_FUNCTION() << ppVar(skinsBefore);
291// ENTER_FUNCTION() << ppVar(skinsAfter);
292// ENTER_FUNCTION() << ppVar(changedSkins);
293
294 for (auto it = changedSkins.begin(); it != changedSkins.end(); ++it) {
295 KIS_SAFE_ASSERT_RECOVER(channel->keyframeAt(*it)) { continue; }
296 rect |= channel->frameExtents(channel->keyframeAt(*it));
297 }
298
299 return rect;
300}
301
303{
304 QRect rect;
305
306 KisRasterKeyframeChannel *channel = device->keyframeChannel(); //TODO: take in channel instead of device...?
307
308 if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
309 return rect;
310 }
311
312 int currentActiveTime = channel->activeKeyframeTime();
313 int prevActiveTime = -1;
314
315 if (currentActiveTime == addedTime) {
316 prevActiveTime = channel->previousKeyframeTime(currentActiveTime);
317 } else {
318 prevActiveTime = currentActiveTime;
319 }
320
321 rect = m_d->updateExtentOnFrameChange(channel,
322 prevActiveTime, addedTime,
323 currentActiveTime, -1);
324
325 return rect;
326}
327
329{
330 m_d->refreshConfig();
331 Q_EMIT sigOnionSkinChanged();
332}
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
const quint8 OPACITY_TRANSPARENT_U8
const QString COMPOSITE_BEHIND
virtual int currentTime() const =0
int numberOfOnionSkins() const
QColor onionSkinTintColorBackward() const
int onionSkinOpacity(int offset, bool requestDefault=false) const
int onionSkinTintFactor() const
bool onionSkinState(int offset) const
QColor onionSkinTintColorForward() const
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
int previousKeyframeTime(const int time) const
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
int activeKeyframeTime(int time) const
Get the time of the active keyframe. Useful for snapping any time to that of the most recent keyframe...
int nextKeyframeTime(const int time) const
QRect calculateFullExtent(const KisPaintDeviceSP device)
QScopedPointer< Private > m_d
QRect calculateExtent(const KisPaintDeviceSP device, int time)
void setColorLabelFilter(QSet< int > colors)
QRect updateExtentOnAddition(const KisPaintDeviceSP device, int addedTime)
void composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect &rect)
static KisOnionSkinCompositor * instance()
KisRasterKeyframeChannel * keyframeChannel() const
void setDefaultPixel(const KoColor &defPixel)
const KoColorSpace * colorSpace() const
KisDefaultBoundsBaseSP defaultBounds() const
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
void setOpacityU8(quint8 opacity)
Set the opacity which is used in painting (like filling polygons)
KisPaintDeviceSP device
void setChannelFlags(QBitArray channelFlags)
void setCompositeOpId(const KoCompositeOp *op)
The KisRasterKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisRas...
QRect frameExtents(KisKeyframeSP keyframe)
The KisRasterKeyframe class is a concrete subclass of KisKeyframe that wraps a physical raster image ...
QBitArray channelFlags(bool color=true, bool alpha=false) const
const KoCompositeOp * compositeOp(const QString &id, const KoColorSpace *srcSpace=nullptr) const
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
QRect updateExtentOnFrameChange(KisRasterKeyframeChannel *channel, int prevActiveTime, int prevIgnoredTime, int nowActiveTime, int nowIgnoredTime)
void tryCompositeFrame(KisRasterKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect)
KisRasterKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, int &outFrame, bool backwards)
KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace)