KisKeyframeSP KisRasterKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode) { int time = keyframeNode.attribute("time").toUInt(); QPoint offset; KisDomUtils::loadValue(keyframeNode, "offset", &offset); QString frameFilename = keyframeNode.attribute("frame"); KisKeyframeSP keyframe; if (m_d->frameFilenames.isEmpty()) { // First keyframe loaded: use the existing frame Q_ASSERT(keyframeCount() == 1); keyframe = constKeys().begin().value(); // Remove from keys. It will get reinserted with new time once we return keys().remove(keyframe->time()); keyframe->setTime(time); m_d->paintDevice->move(offset); } else { KUndo2Command tempCommand; int frameId = m_d->paintDevice->framesInterface()->createFrame(false, 0, offset, &tempCommand); keyframe = toQShared(new KisKeyframe(this, time, frameId)); } setFrameFilename(keyframe->value(), frameFilename); return keyframe; }
QRect KisRasterKeyframeChannel::affectedRect(KisKeyframeSP key) { KeyframesMap::iterator it = keys().find(key->time()); QRect rect; // Calculate changed area as the union of the current and previous keyframe. // This makes sure there are no artifacts left over from the previous frame // where the new one doesn't cover the area. if (it == keys().begin()) { // Using the *next* keyframe at the start of the timeline avoids artifacts // when deleting or moving the first key it++; } else { it--; } if (it != keys().end()) { rect = m_d->paintDevice->framesInterface()->frameBounds(it.value()->value()); } rect |= m_d->paintDevice->framesInterface()->frameBounds(key->value()); if (m_d->onionSkinsEnabled) { const QRect dirtyOnionSkinsRect = KisOnionSkinCompositor::instance()->calculateFullExtent(m_d->paintDevice); rect |= dirtyOnionSkinsRect; } return rect; }
QVariant KisAnimationCurvesModel::data(const QModelIndex &index, int role) const { KisAnimationCurve *curve = m_d->getCurveAt(index); if (curve) { KisScalarKeyframeChannel *channel = curve->channel(); int time = index.column(); KisKeyframeSP keyframe = channel->keyframeAt(time); switch (role) { case SpecialKeyframeExists: return !keyframe.isNull(); case ScalarValueRole: return channel->interpolatedValue(time); case LeftTangentRole: return (keyframe.isNull()) ? QVariant() : keyframe->leftTangent(); case RightTangentRole: return (keyframe.isNull()) ? QVariant() : keyframe->rightTangent(); case InterpolationModeRole: return (keyframe.isNull()) ? QVariant() : keyframe->interpolationMode(); case TangentsModeRole: return (keyframe.isNull()) ? QVariant() : keyframe->tangentsMode(); case CurveColorRole: return curve->color(); case CurveVisibleRole: return curve->visible(); case PreviousKeyframeTime: { KisKeyframeSP active = channel->activeKeyframeAt(time); if (active.isNull()) return QVariant(); if (active->time() < time) { return active->time(); } KisKeyframeSP previous = channel->previousKeyframe(active); if (previous.isNull()) return QVariant(); return previous->time(); } case NextKeyframeTime: { KisKeyframeSP active = channel->activeKeyframeAt(time); if (active.isNull()) { KisKeyframeSP first = channel->firstKeyframe(); if (!first.isNull() && first->time() > time) { return first->time(); } return QVariant(); } KisKeyframeSP next = channel->nextKeyframe(active); if (next.isNull()) return QVariant(); return next->time(); } default: break; } } return KisTimeBasedItemModel::data(index, role); }
KisImageBuilder_Result CSVSaver::encode(const QUrl &uri,const QString &filename) { int idx; int start, end; KisNodeSP node; QByteArray ba; KisKeyframeSP keyframe; QVector<CSVLayerRecord*> layers; KisImageAnimationInterface *animation = m_image->animationInterface(); //open the csv file for writing QFile f(uri.toLocalFile()); if (!f.open(QIODevice::WriteOnly)) { return KisImageBuilder_RESULT_NOT_LOCAL; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //DataStream instead of TextStream for correct line endings QDataStream stream(&f); QString path = filename; if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); path.append(".frames"); //create directory QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } //according to the QT docs, the slash is a universal directory separator path.append("/"); m_image->lock(); node = m_image->rootLayer()->firstChild(); //TODO: correct handling of the layer tree. //for now, only top level paint layers are saved idx = 0; while (node) { if (node->inherits("KisPaintLayer")) { KisPaintLayer* paintLayer = dynamic_cast<KisPaintLayer*>(node.data()); CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.prepend(layerRecord); //reverse order! layerRecord->name = paintLayer->name(); layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_"); if (layerRecord->name.isEmpty()) layerRecord->name= QString("Unnamed-%1").arg(idx); layerRecord->visible = (paintLayer->visible()) ? 1 : 0; layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8; layerRecord->blending = convertToBlending(paintLayer->compositeOpId()); layerRecord->layer = paintLayer; layerRecord->channel = paintLayer->projection()->keyframeChannel(); layerRecord->last = ""; layerRecord->frame = 0; idx++; } node = node->nextSibling(); } KisTimeRange range = animation->fullClipRange(); start = (range.isValid()) ? range.start() : 0; if (!range.isInfinite()) { end = range.end(); if (end < start) end = start; } else { //undefined length, searching for the last keyframe end = start; for (idx = 0; idx < layers.size(); idx++) { keyframe = layers.at(idx)->channel->lastKeyframe(); if ( (!keyframe.isNull()) && (keyframe->time() > end) ) end = keyframe->time(); } } //create temporary doc for exporting QScopedPointer<KisDocument> exportDoc(KisPart::instance()->createDocument()); createTempImage(exportDoc.data()); KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK; if (!m_batchMode) { emit m_doc->statusBarMessage(i18n("Saving CSV file...")); emit m_doc->sigProgress(0); connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int frame = start; int step = 0; do { qApp->processEvents(); if (m_stop) { retval = KisImageBuilder_RESULT_CANCEL; break; } switch(step) { case 0 : //first row if (f.write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) { retval = KisImageBuilder_RESULT_FAILURE; } break; case 1 : //scene header names if (f.write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) { retval = KisImageBuilder_RESULT_FAILURE; } break; case 2 : //scene header values ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8(); if (f.write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8(); if (f.write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8(); if (f.write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } //the framerate is an integer here ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8(); if (f.write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8(); if (f.write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } break; case 3 : //layer header values if (f.write("#Layers") < 0) { //Layers retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8(); if (f.write(ba.data()) < 0) break; } break; case 4 : if (f.write("\r\n#Density") < 0) { //Density retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8(); if (f.write(ba.data()) < 0) break; } break; case 5 : if (f.write("\r\n#Blending") < 0) { //Blending retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8(); if (f.write(ba.data()) < 0) break; } break; case 6 : if (f.write("\r\n#Visible") < 0) { //Visible retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8(); if (f.write(ba.data()) < 0) break; } if (idx < layers.size()) { retval = KisImageBuilder_RESULT_FAILURE; } break; default : //frames if (frame > end) { if (f.write("\r\n") < 0) retval = KisImageBuilder_RESULT_FAILURE; step = 8; break; } ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8(); if (f.write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord *layer = layers.at(idx); keyframe = layer->channel->keyframeAt(frame); if (!keyframe.isNull()) { if (!m_batchMode) { emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 / ((end - start) * layers.size())); } retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx); if (retval != KisImageBuilder_RESULT_OK) break; } ba = QString(", \"%1\"").arg(layer->last).toUtf8(); if (f.write(ba.data()) < 0) break; } if (idx < layers.size()) retval = KisImageBuilder_RESULT_FAILURE; frame++; step = 6; //keep step here break; } step++; } while((retval == KisImageBuilder_RESULT_OK) && (step < 8)); m_image->unlock(); qDeleteAll(layers); f.close(); if (!m_batchMode) { disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); emit m_doc->sigProgress(100); emit m_doc->clearStatusBarMessage(); } QApplication::restoreOverrideCursor(); return retval; }