From 4c0dd6f25ac51a9d9f2d1b0f50a91cae643fad36 Mon Sep 17 00:00:00 2001 From: Jakob Gahde Date: Sat, 4 Nov 2023 00:04:43 +0100 Subject: [PATCH 1/7] Run macdeployqtfix with Python 2 explicitly Changes to the macOS image seem to make this necessary --- .github/actions/create-package/create-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/create-package/create-package.sh b/.github/actions/create-package/create-package.sh index 71a5ddf9f..580c83539 100755 --- a/.github/actions/create-package/create-package.sh +++ b/.github/actions/create-package/create-package.sh @@ -75,7 +75,7 @@ create_package_macos() { echo "::group::Apply macdeployqt fix" curl -fsSLO https://github.com/aurelien-rainone/macdeployqtfix/archive/master.zip bsdtar xf master.zip - python macdeployqtfix-master/macdeployqtfix.py \ + /Library/Frameworks/Python.framework/Versions/2.7/bin/python macdeployqtfix-master/macdeployqtfix.py \ Pencil2D.app/Contents/MacOS/Pencil2D \ /usr/local/Cellar/qt/5.9.1/ echo "::endgroup::" From f7dbcfdeeb3fe08abe05886e3d7efc5adc21df26 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Sun, 12 Nov 2023 08:01:33 -0500 Subject: [PATCH 2/7] Fix deprecated top-level developer_name in AppData XML (#1796) * Fix deprecated top-level developer_name in AppData XML Use the name element in a developer block instead, as recommended by appstreamcli 1.0.0. * Add id property to the AppStream developer element --------- Co-authored-by: Jakob Gahde --- app/data/org.pencil2d.Pencil2D.metainfo.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/data/org.pencil2d.Pencil2D.metainfo.xml b/app/data/org.pencil2d.Pencil2D.metainfo.xml index 4debb60d3..bf7a50781 100644 --- a/app/data/org.pencil2d.Pencil2D.metainfo.xml +++ b/app/data/org.pencil2d.Pencil2D.metainfo.xml @@ -3,7 +3,9 @@ org.pencil2d.Pencil2D org.pencil2d.Pencil2D.desktop Pencil2D - The Pencil2D Team + + The Pencil2D Team + CC0-1.0 GPL-2.0 2D animation software supporting bitmap and vector graphics From a7ab2a60adf4ec9e3f31aa35b9019b3544b5d506 Mon Sep 17 00:00:00 2001 From: Jakob Gahde Date: Fri, 24 Nov 2023 21:46:51 +0100 Subject: [PATCH 3/7] Fix compiler warnings --- core_lib/src/external/macosx/macosx.cpp | 2 ++ core_lib/src/graphics/bitmap/tiledbuffer.h | 4 ++++ core_lib/src/managers/viewmanager.cpp | 7 ++++--- core_lib/src/tool/cameratool.cpp | 4 ++-- core_lib/src/tool/strokemanager.cpp | 5 ++--- core_lib/src/util/util.cpp | 18 +++++++++--------- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/core_lib/src/external/macosx/macosx.cpp b/core_lib/src/external/macosx/macosx.cpp index deb1316bc..a4f1f5435 100644 --- a/core_lib/src/external/macosx/macosx.cpp +++ b/core_lib/src/external/macosx/macosx.cpp @@ -39,7 +39,9 @@ namespace PlatformHandler void initialise() { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif } } diff --git a/core_lib/src/graphics/bitmap/tiledbuffer.h b/core_lib/src/graphics/bitmap/tiledbuffer.h index 7439c628c..3e29cfed8 100644 --- a/core_lib/src/graphics/bitmap/tiledbuffer.h +++ b/core_lib/src/graphics/bitmap/tiledbuffer.h @@ -30,7 +30,11 @@ struct TileIndex { int y; }; +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) inline uint qHash(const TileIndex &key, uint seed) +#else +inline size_t qHash(const TileIndex &key, size_t seed) +#endif { return qHash(key.x, seed) ^ key.y; } diff --git a/core_lib/src/managers/viewmanager.cpp b/core_lib/src/managers/viewmanager.cpp index 928d11d18..9b85848ea 100644 --- a/core_lib/src/managers/viewmanager.cpp +++ b/core_lib/src/managers/viewmanager.cpp @@ -205,11 +205,12 @@ void ViewManager::scaleUp() void ViewManager::scaleDown() { - for (size_t i = gZoomLevels.size() - 1; i >= 0; --i) + const size_t nZoomLevels = gZoomLevels.size(); + for (size_t i = 1; i <= nZoomLevels; i++) { - if (mScaling > gZoomLevels[i]) + if (mScaling > gZoomLevels[nZoomLevels - i]) { - scale(gZoomLevels[i]); + scale(gZoomLevels[nZoomLevels - i]); return; } } diff --git a/core_lib/src/tool/cameratool.cpp b/core_lib/src/tool/cameratool.cpp index 9c74fe31a..e82edb487 100644 --- a/core_lib/src/tool/cameratool.cpp +++ b/core_lib/src/tool/cameratool.cpp @@ -566,9 +566,9 @@ void CameraTool::paintInterpolations(QPainter& painter, const QTransform& worldT painter.save(); QColor color = cameraDotColor; if (currentFrame > frame && currentFrame < nextFrame) - color.setAlphaF(0.5); + color.setAlphaF(.5f); else - color.setAlphaF(0.2); + color.setAlphaF(.2f); painter.setPen(Qt::black); painter.setBrush(color); diff --git a/core_lib/src/tool/strokemanager.cpp b/core_lib/src/tool/strokemanager.cpp index b4e9a6b4f..d94ba58b1 100644 --- a/core_lib/src/tool/strokemanager.cpp +++ b/core_lib/src/tool/strokemanager.cpp @@ -179,7 +179,7 @@ QPointF StrokeManager::interpolateStart(QPointF firstPoint) // Clear queue strokeQueue.clear(); pressureQueue.clear(); - + const int sampleSize = 5; Q_ASSERT(sampleSize > 0); @@ -334,8 +334,7 @@ QList StrokeManager::meanInpolOp(QList points, qreal x, qreal pressure /= strokeQueue.size(); // Use our interpolated points - QPointF mNewInterpolated = mLastInterpolated; - mNewInterpolated = QPointF(x, y); + QPointF mNewInterpolated(x, y); points << mLastPixel << mLastInterpolated << mNewInterpolated << mCurrentPixel; diff --git a/core_lib/src/util/util.cpp b/core_lib/src/util/util.cpp index 39468c04e..622fb4e44 100644 --- a/core_lib/src/util/util.cpp +++ b/core_lib/src/util/util.cpp @@ -19,7 +19,7 @@ GNU General Public License for more details. #include #include -static inline bool clipInfiniteLineToEdge(qreal& t0, qreal& t1, qreal p, qreal q) +static inline bool clipLineToEdge(qreal& t0, qreal& t1, qreal p, qreal q) { if (p < 0) { // Line entering the clipping window t0 = qMax(t0, q / p); @@ -37,14 +37,14 @@ QLineF clipLine(const QLineF& line, const QRect& clip, qreal t0, qreal t1) int left = clip.left(), right = left + clip.width(), top = clip.top(), bottom = top + clip.height(); qreal x1 = line.x1(), x2 = line.x2(), dx = line.dx(), y1 = line.y1(), y2 = line.y2(), dy = line.dy(); - if (t0 == 0 && t1 == 1 && (x1 < left && x2 < left || - x1 > right && x2 > right || - y1 < top && y2 < top || - y1 > bottom && y2 > bottom) || - !clipInfiniteLineToEdge(t0, t1, -dx, x1 - left) || - !clipInfiniteLineToEdge(t0, t1, dx, right - x1) || - !clipInfiniteLineToEdge(t0, t1, -dy, y1 - top) || - !clipInfiniteLineToEdge(t0, t1, dy, bottom - y1)) { + if ((t0 == 0 && t1 == 1 && ((x1 < left && x2 < left) || + (x1 > right && x2 > right) || + (y1 < top && y2 < top) || + (y1 > bottom && y2 > bottom))) || + !clipLineToEdge(t0, t1, -dx, x1 - left) || + !clipLineToEdge(t0, t1, dx, right - x1) || + !clipLineToEdge(t0, t1, -dy, y1 - top) || + !clipLineToEdge(t0, t1, dy, bottom - y1)) { return {}; } From 18c5494f61f228a0f7b8820420627d4ac76231a8 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 25 Nov 2023 13:59:17 +0100 Subject: [PATCH 4/7] Fix vector layer color import (#1798) * Get rid of Object pointers in layer and vector classes * Import colors of imported vector layers * Fix tests --- app/src/importlayersdialog.cpp | 51 +++++++++++++------- core_lib/src/canvaspainter.cpp | 4 +- core_lib/src/graphics/vector/beziercurve.cpp | 22 ++++----- core_lib/src/graphics/vector/beziercurve.h | 2 +- core_lib/src/graphics/vector/vectorimage.cpp | 18 ++----- core_lib/src/graphics/vector/vectorimage.h | 6 +-- core_lib/src/managers/soundmanager.cpp | 51 -------------------- core_lib/src/managers/soundmanager.h | 1 - core_lib/src/structure/layer.cpp | 15 ++---- core_lib/src/structure/layer.h | 13 ++--- core_lib/src/structure/layerbitmap.cpp | 4 +- core_lib/src/structure/layerbitmap.h | 4 +- core_lib/src/structure/layercamera.cpp | 4 +- core_lib/src/structure/layercamera.h | 4 +- core_lib/src/structure/layersound.cpp | 5 +- core_lib/src/structure/layersound.h | 4 +- core_lib/src/structure/layervector.cpp | 6 +-- core_lib/src/structure/layervector.h | 4 +- core_lib/src/structure/object.cpp | 38 +++++++-------- core_lib/src/structure/object.h | 8 +-- tests/src/test_layer.cpp | 21 +++----- tests/src/test_layercamera.cpp | 38 +++------------ 22 files changed, 112 insertions(+), 211 deletions(-) diff --git a/app/src/importlayersdialog.cpp b/app/src/importlayersdialog.cpp index f303072f0..35ae3f71b 100644 --- a/app/src/importlayersdialog.cpp +++ b/app/src/importlayersdialog.cpp @@ -27,6 +27,7 @@ GNU General Public License for more details. #include "soundmanager.h" #include "layer.h" #include "layersound.h" +#include "layervector.h" #include "soundclip.h" @@ -83,29 +84,43 @@ void ImportLayersDialog::importLayers() int currentFrame = mEditor->currentFrame(); Q_ASSERT(ui->lwLayers->count() == mImportObject->getLayerCount()); - for (int i = 0; i < ui->lwLayers->count(); i++ ) + QMap importedColors; + + for (const QListWidgetItem* item : ui->lwLayers->selectedItems()) { - QListWidgetItem* item = ui->lwLayers->item(i); - if (item->isSelected()) - { - int layerId = item->data(Qt::UserRole).toInt(); + mImportLayer = mImportObject->takeLayer(item->data(Qt::UserRole).toInt()); + mImportLayer->setName(mEditor->layers()->nameSuggestLayer(item->text())); + loadKeyFrames(mImportLayer); // all keyframes of this layer must be in memory - mImportLayer = mImportObject->takeLayer(layerId); - mImportLayer->setName(mEditor->layers()->nameSuggestLayer(item->text())); - loadKeyFrames(mImportLayer); // all keyframes of this layer must be in memory + object->addLayer(mImportLayer); - object->addLayer(mImportLayer); + if (mImportLayer->type() == Layer::VECTOR) + { + LayerVector* layerVector = static_cast(mImportLayer); + for (int i = 0; i < mImportObject->getColorCount(); i++) { + if (!layerVector->usesColor(i)) { + continue; + } + + if (!importedColors.contains(i)) { + const ColorRef color = mImportObject->getColor(i); + object->addColor(color); + importedColors[i] = object->getColorCount() - 1; + } + + layerVector->moveColor(i, importedColors[i]); + } + } - if (mImportLayer->type() == Layer::SOUND) + if (mImportLayer->type() == Layer::SOUND) + { + LayerSound* layerSound = static_cast(mImportLayer); + layerSound->foreachKeyFrame([this](KeyFrame* key) { - LayerSound* layerSound = static_cast(mImportLayer); - layerSound->foreachKeyFrame([this](KeyFrame* key) - { - SoundClip* clip = dynamic_cast(key); - Status st = mEditor->sound()->loadSound(clip, clip->fileName()); - Q_ASSERT(st.ok()); - }); - } + SoundClip* clip = dynamic_cast(key); + Status st = mEditor->sound()->loadSound(clip, clip->fileName()); + Q_ASSERT(st.ok()); + }); } } mEditor->object()->modification(); diff --git a/core_lib/src/canvaspainter.cpp b/core_lib/src/canvaspainter.cpp index f03ae616f..24529ec38 100644 --- a/core_lib/src/canvaspainter.cpp +++ b/core_lib/src/canvaspainter.cpp @@ -251,7 +251,7 @@ void CanvasPainter::paintVectorOnionSkinFrame(QPainter& painter, const QRect& bl QPainter onionSkinPainter; initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect); - vectorImage->paintImage(onionSkinPainter, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); + vectorImage->paintImage(onionSkinPainter, *mObject, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, vectorImage->getOpacity()); } @@ -344,7 +344,7 @@ void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blit const bool isDrawing = mTiledBuffer->isValid(); // Paint existing vector image to the painter - vectorImage->paintImage(currentVectorPainter, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); + vectorImage->paintImage(currentVectorPainter, *mObject, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); if (isCurrentLayer) { if (isDrawing) { diff --git a/core_lib/src/graphics/vector/beziercurve.cpp b/core_lib/src/graphics/vector/beziercurve.cpp index 97c2a79d6..ebfe27eea 100644 --- a/core_lib/src/graphics/vector/beziercurve.cpp +++ b/core_lib/src/graphics/vector/beziercurve.cpp @@ -34,7 +34,7 @@ BezierCurve::BezierCurve() BezierCurve::BezierCurve(const QList& pointList, bool smooth) { QList pressureList; - for (int i = 0; i < pointList.size(); i++) + for (int i = 0; i < pointList.size(); i++) { pressureList << 0.5; // default pressure } @@ -297,13 +297,13 @@ BezierCurve BezierCurve::transformed(QTransform transformation) const if (isSelected(-1)) { newOrigin = transformation.map(newOrigin); } transformedCurve.setOrigin( newOrigin ); for(int i=0; i< vertex.size(); i++) { - QPointF newC1 = c1.at(i); - QPointF newC2 = c2.at(i); - QPointF newVertex = vertex.at(i); - if (isSelected(i-1)) { newC1 = transformation.map(newC1); } - if (isSelected(i)) { newC2 = transformation.map(newC2); newVertex = transformation.map(newVertex); } - transformedCurve.appendCubic( newC1, newC2, newVertex, pressure.at(i) ); - if (isSelected(i)) { transformedCurve.setSelected(i, true); } + QPointF newC1 = c1.at(i); + QPointF newC2 = c2.at(i); + QPointF newVertex = vertex.at(i); + if (isSelected(i-1)) { newC1 = transformation.map(newC1); } + if (isSelected(i)) { newC2 = transformation.map(newC2); newVertex = transformation.map(newVertex); } + transformedCurve.appendCubic( newC1, newC2, newVertex, pressure.at(i) ); + if (isSelected(i)) { transformedCurve.setSelected(i, true); } } transformedCurve.setWidth( width); transformedCurve.setVariableWidth( variableWidth ); @@ -429,9 +429,9 @@ void BezierCurve::removeVertex(int i) } } -void BezierCurve::drawPath(QPainter& painter, Object* object, QTransform transformation, bool simplified, bool showThinLines ) +void BezierCurve::drawPath(QPainter& painter, const Object& object, QTransform transformation, bool simplified, bool showThinLines ) { - QColor color = object->getColor(colorNumber).color; + QColor color = object.getColor(colorNumber).color; BezierCurve myCurve; if (isPartlySelected()) { myCurve = (transformed(transformation)); } @@ -869,7 +869,7 @@ bool BezierCurve::findIntersection(BezierCurve curve1, int i1, BezierCurve curve //if (L2.intersect(L1, intersection) == QLineF::BoundedIntersection) { //qDebug() << " FOUND rectangle intersection "; //if (intersectionPoint != curve1.getVertex(i1-1) && intersectionPoint != curve1.getVertex(i1)) { - // qDebug() << " it's not one of the points "; + // qDebug() << " it's not one of the points "; // find the cubic intersection int nSteps = 24; P1 = curve1.getVertex(i1-1); diff --git a/core_lib/src/graphics/vector/beziercurve.h b/core_lib/src/graphics/vector/beziercurve.h index 0f1b67f30..47297f57f 100644 --- a/core_lib/src/graphics/vector/beziercurve.h +++ b/core_lib/src/graphics/vector/beziercurve.h @@ -89,7 +89,7 @@ class BezierCurve QPainterPath getStrokedPath(qreal width, bool pressure); QRectF getBoundingRect(); - void drawPath(QPainter& painter, Object* object, QTransform transformation, bool simplified, bool showThinLines ); + void drawPath(QPainter& painter, const Object& object, QTransform transformation, bool simplified, bool showThinLines ); void createCurve(const QList& pointList, const QList& pressureList , bool smooth); void smoothCurve(); diff --git a/core_lib/src/graphics/vector/vectorimage.cpp b/core_lib/src/graphics/vector/vectorimage.cpp index 9dfe0980c..061b36c2b 100644 --- a/core_lib/src/graphics/vector/vectorimage.cpp +++ b/core_lib/src/graphics/vector/vectorimage.cpp @@ -33,7 +33,6 @@ VectorImage::VectorImage() VectorImage::VectorImage(const VectorImage& v2) : KeyFrame(v2) { deselectAll(); - mObject = v2.mObject; mCurves = v2.mCurves; mArea = v2.mArea; mOpacity = v2.mOpacity; @@ -52,7 +51,6 @@ VectorImage& VectorImage::operator=(const VectorImage& a) { deselectAll(); KeyFrame::operator=(a); - mObject = a.mObject; mCurves = a.mCurves; mArea = a.mArea; mOpacity = a.mOpacity; @@ -1090,16 +1088,6 @@ void VectorImage::paste(VectorImage& vectorImage) modification(); } -/** - * @brief VectorImage::getColor - * @param colorNumber: the color number which is referred to in the palette - * @return QColor - */ -QColor VectorImage::getColor(int colorNumber) -{ - return mObject->getColor(colorNumber).color; -} - /** * @brief VectorImage::getColorNumber * @param point: The QPoint of the BezierArea @@ -1194,11 +1182,13 @@ void VectorImage::moveColor(int start, int end) /** * @brief VectorImage::paintImage * @param painter: QPainter& + * @param object: const Object& * @param simplified: bool * @param showThinCurves: bool * @param antialiasing: bool */ void VectorImage::paintImage(QPainter& painter, + const Object& object, bool simplified, bool showThinCurves, bool antialiasing) @@ -1220,7 +1210,7 @@ void VectorImage::paintImage(QPainter& painter, updateArea(mArea[i]); // to do: if selected // --- fill areas ---- // - QColor color = getColor(mArea[i].mColorNumber); + QColor color = object.getColor(mArea[i].mColorNumber).color; painter.save(); painter.setWorldMatrixEnabled(false); @@ -1246,7 +1236,7 @@ void VectorImage::paintImage(QPainter& painter, // ---- draw curves ---- for (BezierCurve curve : mCurves) { - curve.drawPath(painter, mObject, mSelectionTransformation, simplified, showThinCurves); + curve.drawPath(painter, object, mSelectionTransformation, simplified, showThinCurves); painter.setClipping(false); } painter.restore(); diff --git a/core_lib/src/graphics/vector/vectorimage.h b/core_lib/src/graphics/vector/vectorimage.h index ec97458a5..8f9769deb 100644 --- a/core_lib/src/graphics/vector/vectorimage.h +++ b/core_lib/src/graphics/vector/vectorimage.h @@ -38,8 +38,6 @@ class VectorImage : public KeyFrame VectorImage* clone() const override; - void setObject(Object* pObj) { mObject = pObj; } - bool read(QString filePath); Status write(QString filePath, QString format); @@ -85,7 +83,6 @@ class VectorImage : public KeyFrame void paste(VectorImage&); - QColor getColor(int i); int getColorNumber(QPointF point); bool usesColor(int index); void removeColor(int index); @@ -93,7 +90,7 @@ class VectorImage : public KeyFrame bool isCurveVisible(int curve); void moveColor(int start, int end); - void paintImage(QPainter& painter, bool simplified, bool showThinCurves, bool antialiasing); + void paintImage(QPainter& painter, const Object& object, bool simplified, bool showThinCurves, bool antialiasing); void clear(); void clean(); @@ -165,7 +162,6 @@ class VectorImage : public KeyFrame private: QList mCurves; - Object* mObject = nullptr; QRectF mSelectionRect; QTransform mSelectionTransformation; QSize mSize; diff --git a/core_lib/src/managers/soundmanager.cpp b/core_lib/src/managers/soundmanager.cpp index 655d51307..8bcc3843c 100644 --- a/core_lib/src/managers/soundmanager.cpp +++ b/core_lib/src/managers/soundmanager.cpp @@ -68,57 +68,6 @@ Status SoundManager::save(Object*) return Status::OK; } -Status SoundManager::loadSound(Layer* soundLayer, int frameNumber, QString soundFilePath) -{ - Q_ASSERT(soundLayer); - if (soundLayer->type() != Layer::SOUND) - { - return Status::ERROR_INVALID_LAYER_TYPE; - } - - if (frameNumber < 0) - { - return Status::ERROR_INVALID_FRAME_NUMBER; - } - - if (!QFile::exists(soundFilePath)) - { - return Status::FILE_NOT_FOUND; - } - - KeyFrame* key = soundLayer->getKeyFrameAt(frameNumber); - if (key == nullptr) - { - key = new SoundClip; - soundLayer->addKeyFrame(frameNumber, key); - } - - if (!key->fileName().isEmpty()) - { - // file path should be empty. - // we can only load a audio clip to an empty key! - return Status::FAIL; - } - - QString strCopyFile = soundLayer->object()->copyFileToDataFolder(soundFilePath); - Q_ASSERT(!strCopyFile.isEmpty()); - - QString sOriginalName = QFileInfo(soundFilePath).fileName(); - - SoundClip* soundClip = dynamic_cast(key); - soundClip->init(strCopyFile); - soundClip->setSoundClipName(sOriginalName); - - Status st = createMediaPlayer(soundClip); - if (!st.ok()) - { - delete soundClip; - return st; - } - - return Status::OK; -} - Status SoundManager::loadSound(SoundClip* soundClip, QString strSoundFile) { Q_ASSERT(soundClip); diff --git a/core_lib/src/managers/soundmanager.h b/core_lib/src/managers/soundmanager.h index 31abc12d5..4085ad3b5 100644 --- a/core_lib/src/managers/soundmanager.h +++ b/core_lib/src/managers/soundmanager.h @@ -38,7 +38,6 @@ class SoundManager : public BaseManager Status load(Object*) override; Status save(Object*) override; - Status loadSound(Layer* soundLayer, int frameNumber, QString strSoundFile); Status loadSound(SoundClip* soundClip, QString strSoundFile); Status processSound(SoundClip* soundClip); diff --git a/core_lib/src/structure/layer.cpp b/core_lib/src/structure/layer.cpp index 1e5f43553..f272e5912 100644 --- a/core_lib/src/structure/layer.cpp +++ b/core_lib/src/structure/layer.cpp @@ -22,7 +22,6 @@ GNU General Public License for more details. #include #include #include "keyframe.h" -#include "object.h" // Used to sort the selected frames list bool sortAsc(int left, int right) @@ -30,15 +29,14 @@ bool sortAsc(int left, int right) return left < right; } -Layer::Layer(Object* object, LAYER_TYPE eType) +Layer::Layer(int id, LAYER_TYPE eType) { Q_ASSERT(eType != UNDEFINED); - mObject = object; meType = eType; mName = QString(tr("Undefined Layer")); - mId = object->getUniqueLayerID(); + mId = id; } Layer::~Layer() @@ -51,13 +49,6 @@ Layer::~Layer() mKeyFrames.clear(); } -void Layer::setObject(Object* obj) -{ - Q_ASSERT(obj); - mObject = obj; - mId = mObject->getUniqueLayerID(); -} - void Layer::foreachKeyFrame(std::function action) const { for (auto pair : mKeyFrames) @@ -180,7 +171,7 @@ bool Layer::addNewKeyFrameAt(int position) { if (position <= 0) return false; - KeyFrame* key = createKeyFrame(position, mObject); + KeyFrame* key = createKeyFrame(position); return addKeyFrame(position, key); } diff --git a/core_lib/src/structure/layer.h b/core_lib/src/structure/layer.h index bbb857cf8..0e82e2b1d 100644 --- a/core_lib/src/structure/layer.h +++ b/core_lib/src/structure/layer.h @@ -28,11 +28,10 @@ class QMouseEvent; class QPainter; class KeyFrame; -class Object; class TimeLineCells; class Status; -#define ProgressCallback std::function +typedef std::function ProgressCallback; class Layer : public QObject { @@ -49,15 +48,13 @@ class Layer : public QObject CAMERA = 5, }; - explicit Layer(Object*, LAYER_TYPE); + explicit Layer(int id, LAYER_TYPE eType); ~Layer() override; int id() const { return mId; } + void setId(int layerId) { mId = layerId; } LAYER_TYPE type() const { return meType; } - Object* object() const { return mObject; } - void setObject(Object* obj); - void setName(QString name) { mName = name; } QString name() const { return mName; } @@ -168,15 +165,13 @@ class Layer : public QObject void clearDirtyFrames() { mDirtyFrames.clear(); } protected: - void setId(int LayerId) { mId = LayerId; } - virtual KeyFrame* createKeyFrame(int position, Object*) = 0; + virtual KeyFrame* createKeyFrame(int position) = 0; bool loadKey(KeyFrame*); private: void removeFromSelectionList(int position); LAYER_TYPE meType = UNDEFINED; - Object* mObject = nullptr; int mId = 0; bool mVisible = true; QString mName; diff --git a/core_lib/src/structure/layerbitmap.cpp b/core_lib/src/structure/layerbitmap.cpp index 119464046..d5eb0cc62 100644 --- a/core_lib/src/structure/layerbitmap.cpp +++ b/core_lib/src/structure/layerbitmap.cpp @@ -23,7 +23,7 @@ GNU General Public License for more details. #include "bitmapimage.h" -LayerBitmap::LayerBitmap(Object* object) : Layer(object, Layer::BITMAP) +LayerBitmap::LayerBitmap(int id) : Layer(id, Layer::BITMAP) { setName(tr("Bitmap Layer")); } @@ -99,7 +99,7 @@ Status LayerBitmap::saveKeyFrameFile(KeyFrame* keyframe, QString path) return Status::OK; } -KeyFrame* LayerBitmap::createKeyFrame(int position, Object*) +KeyFrame* LayerBitmap::createKeyFrame(int position) { BitmapImage* b = new BitmapImage; b->setPos(position); diff --git a/core_lib/src/structure/layerbitmap.h b/core_lib/src/structure/layerbitmap.h index 4865dd64a..8ae10fe1a 100644 --- a/core_lib/src/structure/layerbitmap.h +++ b/core_lib/src/structure/layerbitmap.h @@ -27,7 +27,7 @@ class LayerBitmap : public Layer Q_OBJECT public: - LayerBitmap(Object* object); + explicit LayerBitmap(int id); ~LayerBitmap() override; QDomElement createDomElement(QDomDocument& doc) const override; @@ -42,7 +42,7 @@ class LayerBitmap : public Layer protected: Status saveKeyFrameFile(KeyFrame*, QString strPath) override; - KeyFrame* createKeyFrame(int position, Object*) override; + KeyFrame* createKeyFrame(int position) override; private: void loadImageAtFrame(QString strFilePath, QPoint topLeft, int frameNumber, qreal opacity); diff --git a/core_lib/src/structure/layercamera.cpp b/core_lib/src/structure/layercamera.cpp index c41507f5b..b072954e0 100644 --- a/core_lib/src/structure/layercamera.cpp +++ b/core_lib/src/structure/layercamera.cpp @@ -22,7 +22,7 @@ GNU General Public License for more details. #include "camera.h" #include "pencildef.h" -LayerCamera::LayerCamera(Object* object) : Layer(object, Layer::CAMERA) +LayerCamera::LayerCamera(int id) : Layer(id, Layer::CAMERA) { setName(tr("Camera Layer")); @@ -539,7 +539,7 @@ Status LayerCamera::saveKeyFrameFile(KeyFrame*, QString) return Status::OK; } -KeyFrame* LayerCamera::createKeyFrame(int position, Object*) +KeyFrame* LayerCamera::createKeyFrame(int position) { Camera* c = new Camera; c->setPos(position); diff --git a/core_lib/src/structure/layercamera.h b/core_lib/src/structure/layercamera.h index 0ef4cc151..54773ecb6 100644 --- a/core_lib/src/structure/layercamera.h +++ b/core_lib/src/structure/layercamera.h @@ -29,7 +29,7 @@ class Camera; class LayerCamera : public Layer { public: - explicit LayerCamera(Object* object); + explicit LayerCamera(int id); ~LayerCamera() override; void loadImageAtFrame(int frame, qreal dx, qreal dy, qreal rotate, qreal scale, CameraEasingType easing, const QPointF& pathPoint, bool pathMoved); @@ -70,7 +70,7 @@ class LayerCamera : public Layer protected: Status saveKeyFrameFile(KeyFrame*, QString path) override; - KeyFrame* createKeyFrame(int position, Object*) override; + KeyFrame* createKeyFrame(int position) override; private: void linearInterpolateTransform(Camera*); diff --git a/core_lib/src/structure/layersound.cpp b/core_lib/src/structure/layersound.cpp index 83b46bc50..c3c88751d 100644 --- a/core_lib/src/structure/layersound.cpp +++ b/core_lib/src/structure/layersound.cpp @@ -20,11 +20,10 @@ GNU General Public License for more details. #include #include #include -#include "object.h" #include "soundclip.h" -LayerSound::LayerSound(Object* object) : Layer(object, Layer::SOUND) +LayerSound::LayerSound(int id) : Layer(id, Layer::SOUND) { setName(tr("Sound Layer")); } @@ -155,7 +154,7 @@ Status LayerSound::saveKeyFrameFile(KeyFrame* key, QString path) return Status::OK; } -KeyFrame* LayerSound::createKeyFrame(int position, Object*) +KeyFrame* LayerSound::createKeyFrame(int position) { SoundClip* s = new SoundClip; s->setPos(position); diff --git a/core_lib/src/structure/layersound.h b/core_lib/src/structure/layersound.h index 718c6fc38..ddcf8c6fa 100644 --- a/core_lib/src/structure/layersound.h +++ b/core_lib/src/structure/layersound.h @@ -27,7 +27,7 @@ class LayerSound : public Layer Q_OBJECT public: - LayerSound( Object* object ); + explicit LayerSound(int id); ~LayerSound(); QDomElement createDomElement(QDomDocument& doc) const override; void loadDomElement(const QDomElement& element, QString dataDirPath, ProgressCallback progressStep) override; @@ -39,7 +39,7 @@ class LayerSound : public Layer protected: Status saveKeyFrameFile(KeyFrame*, QString path) override; - KeyFrame* createKeyFrame(int position, Object*) override; + KeyFrame* createKeyFrame(int position) override; }; #endif diff --git a/core_lib/src/structure/layervector.cpp b/core_lib/src/structure/layervector.cpp index 57ea14443..3efd51e9f 100644 --- a/core_lib/src/structure/layervector.cpp +++ b/core_lib/src/structure/layervector.cpp @@ -22,7 +22,7 @@ GNU General Public License for more details. #include -LayerVector::LayerVector(Object* object) : Layer(object, Layer::VECTOR) +LayerVector::LayerVector(int id) : Layer(id, Layer::VECTOR) { setName(tr("Vector Layer")); } @@ -70,7 +70,6 @@ void LayerVector::loadImageAtFrame(QString path, int frameNumber) } VectorImage* vecImg = new VectorImage; vecImg->setPos(frameNumber); - vecImg->setObject(object()); vecImg->read(path); addKeyFrame(frameNumber, vecImg); } @@ -106,11 +105,10 @@ Status LayerVector::saveKeyFrameFile(KeyFrame* keyFrame, QString path) return Status::OK; } -KeyFrame* LayerVector::createKeyFrame(int position, Object* obj) +KeyFrame* LayerVector::createKeyFrame(int position) { VectorImage* v = new VectorImage; v->setPos(position); - v->setObject(obj); return v; } diff --git a/core_lib/src/structure/layervector.h b/core_lib/src/structure/layervector.h index 563a1800b..163cdc0a4 100644 --- a/core_lib/src/structure/layervector.h +++ b/core_lib/src/structure/layervector.h @@ -27,7 +27,7 @@ class LayerVector : public Layer Q_OBJECT public: - LayerVector(Object* object); + explicit LayerVector(int id); ~LayerVector(); // method from layerImage @@ -45,7 +45,7 @@ class LayerVector : public Layer protected: Status saveKeyFrameFile(KeyFrame*, QString path) override; - KeyFrame* createKeyFrame(int position, Object*) override; + KeyFrame* createKeyFrame(int position) override; private: QString fileName(KeyFrame* key) const; diff --git a/core_lib/src/structure/object.cpp b/core_lib/src/structure/object.cpp index 30391fed5..17834e6f7 100644 --- a/core_lib/src/structure/object.cpp +++ b/core_lib/src/structure/object.cpp @@ -97,16 +97,16 @@ bool Object::loadXML(const QDomElement& docElem, ProgressCallback progressForwar switch (element.attribute("type").toInt()) { case Layer::BITMAP: - newLayer = new LayerBitmap(this); + newLayer = new LayerBitmap(getUniqueLayerID()); break; case Layer::VECTOR: - newLayer = new LayerVector(this); + newLayer = new LayerVector(getUniqueLayerID()); break; case Layer::SOUND: - newLayer = new LayerSound(this); + newLayer = new LayerSound(getUniqueLayerID()); break; case Layer::CAMERA: - newLayer = new LayerCamera(this); + newLayer = new LayerCamera(getUniqueLayerID()); break; default: Q_UNREACHABLE(); @@ -119,7 +119,7 @@ bool Object::loadXML(const QDomElement& docElem, ProgressCallback progressForwar LayerBitmap* Object::addNewBitmapLayer() { - LayerBitmap* layerBitmap = new LayerBitmap(this); + LayerBitmap* layerBitmap = new LayerBitmap(getUniqueLayerID()); mLayers.append(layerBitmap); layerBitmap->addNewKeyFrameAt(1); @@ -129,7 +129,7 @@ LayerBitmap* Object::addNewBitmapLayer() LayerVector* Object::addNewVectorLayer() { - LayerVector* layerVector = new LayerVector(this); + LayerVector* layerVector = new LayerVector(getUniqueLayerID()); mLayers.append(layerVector); layerVector->addNewKeyFrameAt(1); @@ -139,7 +139,7 @@ LayerVector* Object::addNewVectorLayer() LayerSound* Object::addNewSoundLayer() { - LayerSound* layerSound = new LayerSound(this); + LayerSound* layerSound = new LayerSound(getUniqueLayerID()); mLayers.append(layerSound); // No default keyFrame at position 1 for Sound layer. @@ -149,7 +149,7 @@ LayerSound* Object::addNewSoundLayer() LayerCamera* Object::addNewCameraLayer() { - LayerCamera* layerCamera = new LayerCamera(this); + LayerCamera* layerCamera = new LayerCamera(getUniqueLayerID()); mLayers.append(layerCamera); layerCamera->addNewKeyFrameAt(1); @@ -378,7 +378,7 @@ bool Object::addLayer(Layer* layer) { return false; } - layer->setObject(this); + layer->setId(getUniqueLayerID()); mLayers.append(layer); return true; } @@ -456,7 +456,7 @@ void Object::removeColor(int index) mPalette.removeAt(index); - // update the vector pictures using that color ! + // update the vector pictures using that color! } void Object::renameColor(int i, const QString& text) @@ -507,8 +507,8 @@ void Object::exportPalettePencil(QFile& file) const tag.setAttribute("alpha", ref.color.alpha()); root.appendChild(tag); } - int IndentSize = 2; - doc.save(out, IndentSize); + int indentSize = 2; + doc.save(out, indentSize); } bool Object::exportPalette(const QString& filePath) const @@ -542,7 +542,7 @@ void Object::importPaletteGPL(QFile& file) QTextStream in(&file); QString line; - // First line must start with "GIMP Palette" + // The first line must start with "GIMP Palette" // Displaying an error here would be nice in.readLineInto(&line); if (!line.startsWith("GIMP Palette")) return; @@ -553,16 +553,16 @@ void Object::importPaletteGPL(QFile& file) if (line.startsWith("Name: ")) { in.readLineInto(&line); - // The new format contains an optional thrid line starting with "Columns: " + // The new format contains an optional third line starting with "Columns: " if (line.startsWith("Columns: ")) { - // Skip to next line + // Skip to the next line in.readLineInto(&line); } } // Colors inherit the value from the previous color for missing channels - // Some palettes may rely on this behavior so we should try to replicate it + // Some palettes may rely on this behavior, so we should try to replicate it QColor prevColor(Qt::black); do @@ -606,7 +606,7 @@ void Object::importPaletteGPL(QFile& file) if (countInLine < 2) green = prevColor.green(); if (countInLine < 3) blue = prevColor.blue(); - // GIMP assigns colors the name "Untitled" by default now + // GIMP assigns colors the name "Untitled" by default now, // so in addition to missing names, we also use automatic // naming for this if (name.isEmpty() || name == "Untitled") name = QString(); @@ -755,7 +755,7 @@ void Object::paintImage(QPainter& painter,int frameNumber, if (vec) { painter.setOpacity(vec->getOpacity()); - vec->paintImage(painter, false, false, antialiasing); + vec->paintImage(painter, *this, false, false, antialiasing); } } } @@ -815,7 +815,7 @@ bool Object::exportFrames(int frameStart, int frameEnd, { format = "JPG"; extension = ".jpg"; - transparency = false; // JPG doesn't support transparency so we have to include the background + transparency = false; // JPG doesn't support transparency, so we have to include the background } if (formatStr == "TIFF" || formatStr == "tiff" || formatStr == "TIF" || formatStr == "tif") { diff --git a/core_lib/src/structure/object.h b/core_lib/src/structure/object.h index 6ed45a318..c7701d06b 100644 --- a/core_lib/src/structure/object.h +++ b/core_lib/src/structure/object.h @@ -45,10 +45,10 @@ class Object final explicit Object(); ~Object(); - Object(Object const&) = delete; - Object(Object&&) = delete; - Object& operator=(Object const&) = delete; - Object& operator=(Object&&) = delete; + Object(Object const&) = delete; + Object(Object&&) = delete; + Object& operator=(Object const&) = delete; + Object& operator=(Object&&) = delete; void init(); void createWorkingDir(); diff --git a/tests/src/test_layer.cpp b/tests/src/test_layer.cpp index 39eb0be60..73478d105 100644 --- a/tests/src/test_layer.cpp +++ b/tests/src/test_layer.cpp @@ -26,43 +26,37 @@ GNU General Public License for more details. TEST_CASE("LayerType") { - Object* object = new Object; - SECTION("Bitmap Layer") { - Layer* bitmapLayer = new LayerBitmap(object); + Layer* bitmapLayer = new LayerBitmap(1); REQUIRE(bitmapLayer->type() == Layer::BITMAP); delete bitmapLayer; } SECTION("Vector Layer") { - Layer* vecLayer = new LayerVector(object); + Layer* vecLayer = new LayerVector(2); REQUIRE(vecLayer->type() == Layer::VECTOR); delete vecLayer; } SECTION("Camera Layer") { - Layer* cameraLayer = new LayerCamera(object); + Layer* cameraLayer = new LayerCamera(3); REQUIRE(cameraLayer->type() == Layer::CAMERA); delete cameraLayer; } SECTION("Sound Layer") { - Layer* soundLayer = new LayerSound(object); + Layer* soundLayer = new LayerSound(4); REQUIRE(soundLayer->type() == Layer::SOUND); delete soundLayer; } - - delete object; } SCENARIO("Add key frames into a Layer", "[Layer]") { - Object* object = new Object; - GIVEN("A Bitmap Layer") { - Layer* layer = new LayerBitmap(object); + Layer* layer = new LayerBitmap(1); REQUIRE(layer->addNewKeyFrameAt(0) == false); // first key position is 1. REQUIRE(layer->keyFrameCount() == 0); @@ -96,7 +90,7 @@ SCENARIO("Add key frames into a Layer", "[Layer]") GIVEN("A Vector Layer") { - Layer* layer = new LayerVector(object); + Layer* layer = new LayerVector(2); REQUIRE(layer->addNewKeyFrameAt(0) == false); // first key position is 1. REQUIRE(layer->keyFrameCount() == 0); @@ -117,7 +111,7 @@ SCENARIO("Add key frames into a Layer", "[Layer]") GIVEN("A Camera Layer") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(3); REQUIRE(layer->addNewKeyFrameAt(0) == false); // first key position is 1. REQUIRE(layer->keyFrameCount() == 0); @@ -135,7 +129,6 @@ SCENARIO("Add key frames into a Layer", "[Layer]") } delete layer; } - delete object; } TEST_CASE("Test Layer::keyExists()", "[Layer]") diff --git a/tests/src/test_layercamera.cpp b/tests/src/test_layercamera.cpp index c37afdc26..f6899d73f 100644 --- a/tests/src/test_layercamera.cpp +++ b/tests/src/test_layercamera.cpp @@ -26,11 +26,9 @@ GNU General Public License for more details. SCENARIO("Create camera keyframe with linear easing") { - Object* object = new Object; - GIVEN("A Camera Layer with one keyframe") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(1); LayerCamera* camLayer = static_cast(layer); WHEN("Adding a keyframe") @@ -46,17 +44,13 @@ SCENARIO("Create camera keyframe with linear easing") } } } - - delete object; } SCENARIO("Add a second keyframe and see that the path point of the first keyframe is updated") { - Object* object = new Object; - GIVEN("A Camera layer with multiple keyframes") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(1); LayerCamera* camLayer = static_cast(layer); layer->addNewKeyFrameAt(1); @@ -75,17 +69,13 @@ SCENARIO("Add a second keyframe and see that the path point of the first keyfram } } } - - delete object; } SCENARIO("Add keyframe after having interpolated the previous keyframe and see that the translation is kept") { - Object* object = new Object; - GIVEN("A Camera layer with multiple keyframes") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(1); LayerCamera* camLayer = static_cast(layer); layer->addNewKeyFrameAt(1); @@ -102,17 +92,13 @@ SCENARIO("Add keyframe after having interpolated the previous keyframe and see t } } } - - delete object; } SCENARIO("Remove a camera keyframe and see that the path is properly reset") { - Object* object = new Object; - GIVEN("A Camera layer with multiple keyframes") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(1); LayerCamera* camLayer = static_cast(layer); layer->addNewKeyFrameAt(1); @@ -132,17 +118,13 @@ SCENARIO("Remove a camera keyframe and see that the path is properly reset") } } } - - delete object; } SCENARIO("When deleting an in between keyframe, the previous keyframe will try to recover its initial control point, if possible") { - Object* object = new Object; - GIVEN("A Camera layer with multiple keyframes where the keys are added sequentially") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(1); LayerCamera* camLayer = static_cast(layer); layer->addNewKeyFrameAt(1); @@ -169,7 +151,7 @@ SCENARIO("When deleting an in between keyframe, the previous keyframe will try t GIVEN("A Camera layer with multiple keyframes where the third frame is added in-between") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(2); LayerCamera* camLayer = static_cast(layer); layer->addNewKeyFrameAt(1); @@ -196,17 +178,13 @@ SCENARIO("When deleting an in between keyframe, the previous keyframe will try t } } } - - delete object; } SCENARIO("When adding a keyframe in-between two othes where the control points has been modified, the curve will be preserved") { - Object* object = new Object; - GIVEN("A Camera layer with multiple keyframes where the third frame is added in-between") { - Layer* layer = new LayerCamera(object); + Layer* layer = new LayerCamera(1); LayerCamera* camLayer = static_cast(layer); layer->addNewKeyFrameAt(1); @@ -233,8 +211,6 @@ SCENARIO("When adding a keyframe in-between two othes where the control points h } } } - - delete object; } SCENARIO("Loading a project and see that all camera properties are set, if applicable") From 8c3728f94ba432856b6c6bf1319ef34f8361bc40 Mon Sep 17 00:00:00 2001 From: Jakob Gahde Date: Sat, 9 Dec 2023 22:55:28 +0100 Subject: [PATCH 5/7] Mention the Qt 6 Multimedia requirement in build guides --- docs/build_linux.md | 2 +- docs/build_mac.md | 1 + docs/build_win.md | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/build_linux.md b/docs/build_linux.md index c591ec298..81195359d 100644 --- a/docs/build_linux.md +++ b/docs/build_linux.md @@ -21,7 +21,7 @@ Pencil2D relies on the %Qt application framework so you must install it before y - Executing this file will start the %Qt installer application. If you can't open it right away, you may have to right click on it and go to *Properties*, then in the *Permissions* tab select *Allow executing file as program* and then try opening it again. - Click Next. You have to create a free %Qt account if you don't have one. Don't worry, it won't cost you a penny. - Next, specify a location for %Qt, put it somewhere you can find it in case you ever need to navigate to the %Qt files manually. -- Next, you can select the components you wish to install. At the very least you should have Desktop GCC selected under the latest %Qt version. Also make sure %Qt Creator under the Tools section is being installed. +- Next, you can select the components you wish to install. At the very least you should have Desktop GCC selected under the latest %Qt version, as well as its Multimedia module from the Additional Libraries section if you are using Qt 6. Also make sure %Qt Creator under the Tools section is being installed. - Agree to the license and begin the installation. It will take a long time to download all of the files, so be patient. When the installation is complete, press Done and it will launch %Qt Creator for you. ##### Command-line method diff --git a/docs/build_mac.md b/docs/build_mac.md index 7ce2127a9..0c3897af7 100644 --- a/docs/build_mac.md +++ b/docs/build_mac.md @@ -43,6 +43,7 @@ A dialog should pop up asking if you want to install the command line developer - Next, specify a location for %Qt, put it somewhere you can find it in case you ever need to navigate to the %Qt files manually. - Then choose the %Qt version and components you wish to install. - If you have no idea what to do, select `%Qt 5.15.x -> macOS`. + - If you are using Qt 6, make sure to also select its Multimedia module in the Additional Libraries section. - Also make sure %Qt Creator under the Tools section is being installed. - Agree to the license and begin the installation. It will take a long time to download all of the files, so be patient. When the installation is complete, press `Done` and it will launch %Qt Creator for you. diff --git a/docs/build_win.md b/docs/build_win.md index 2364ac3f2..9b467b40c 100644 --- a/docs/build_win.md +++ b/docs/build_win.md @@ -26,6 +26,7 @@ Pencil2D is built upon Qt, you need to install it before you can compile the pro - You have to create a free Qt account if you don't have one. Don't worry, it won't cost you a penny. - In the next step, choose the Qt version that matches your C++ compiler. - For example, select `MSVC 2019 64-bit` if you have Visual C++ 2019 installed. + - If you are using Qt 6, make sure to also select its Multimedia module in the Additional Libraries section. - If you have no idea what to do, select the latest `Qt 5.15.x -> MinGW 8.x` and `Developer and Design Tools -> MinGW 8.x 64-bit`. - Agree to the license and start the installation. It will take a long time to download all of the files, so be patient. When the installation is complete, press `Done` and it will launch Qt Creator for you. From 363dcd79c6d772abb126b9df22a5369910c21ffb Mon Sep 17 00:00:00 2001 From: Oliver Stevns <1045397+MrStevns@users.noreply.github.com> Date: Sun, 10 Dec 2023 14:56:30 +0100 Subject: [PATCH 6/7] Regression - #1799: Fix pen dabs being added to each other (#1800) Actually the first check in canvas painter would always return true because the blitRect and mTilledBuffer rect are in different coordinate spaces. --- core_lib/src/canvaspainter.cpp | 11 ++------- core_lib/src/canvaspainter.h | 3 --- core_lib/src/interface/scribblearea.cpp | 33 +++++++++++++++---------- core_lib/src/tool/erasertool.cpp | 4 +-- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/core_lib/src/canvaspainter.cpp b/core_lib/src/canvaspainter.cpp index 24529ec38..3990e3920 100644 --- a/core_lib/src/canvaspainter.cpp +++ b/core_lib/src/canvaspainter.cpp @@ -300,16 +300,9 @@ void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blit if (isCurrentLayer && isDrawing) { - // Certain tools require being painted continuously, for example, the Polyline tool. - // The tiled buffer does not update the area outside which it paints, - // so in that case, in order to see the previously laid-down polyline stroke, - // the surrounding area must be drawn again before - // applying the new tiled output on top - if (!blitRect.contains(mTiledBuffer->bounds()) || mOptions.bIgnoreCanvasBuffer) { - currentBitmapPainter.setCompositionMode(QPainter::CompositionMode_Source); - currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image()); - } + currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image()); + currentBitmapPainter.setCompositionMode(mOptions.cmBufferBlendMode); const auto tiles = mTiledBuffer->tiles(); for (const Tile* tile : tiles) { currentBitmapPainter.drawPixmap(tile->posF(), tile->pixmap()); diff --git a/core_lib/src/canvaspainter.h b/core_lib/src/canvaspainter.h index 99b90c6be..7f1d3c285 100644 --- a/core_lib/src/canvaspainter.h +++ b/core_lib/src/canvaspainter.h @@ -42,9 +42,6 @@ struct CanvasPainterOptions bool bThinLines = false; bool bOutlines = false; - /// When using a tool that can't rely on canvas buffer, - /// for example Polyline because we're continously clearing the surface - bool bIgnoreCanvasBuffer = false; LayerVisibility eLayerVisibility = LayerVisibility::RELATED; float fLayerVisibilityThreshold = 0.f; float scaling = 1.0f; diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index 4e8d5e319..d39359c02 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -202,16 +202,6 @@ void ScribbleArea::onTileUpdated(TiledBuffer* tiledBuffer, Tile* tile) void ScribbleArea::onTileCreated(TiledBuffer* tiledBuffer, Tile* tile) { Q_UNUSED(tiledBuffer) - Layer::LAYER_TYPE layerType = mEditor->layers()->currentLayer()->type(); - if (layerType == Layer::BITMAP) { - const auto& bitmapImage = currentBitmapImage(mEditor->layers()->currentLayer()); - const QImage& image = *bitmapImage->image(); - tile->load(image, bitmapImage->topLeft()); - } else if (layerType == Layer::VECTOR) { - - // Not used, we only use the buffer to paint the stroke before painting the real vector stroke - } - const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds())); update(mappedRect.toAlignedRect()); } @@ -855,7 +845,24 @@ void ScribbleArea::paintBitmapBuffer() BitmapImage* targetImage = currentBitmapImage(layer); if (targetImage != nullptr) { - targetImage->paste(&mTiledBuffer, QPainter::CompositionMode_Source); + QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver; + switch (currentTool()->type()) + { + case ERASER: + cm = QPainter::CompositionMode_DestinationOut; + break; + case BRUSH: + case PEN: + case PENCIL: + if (currentTool()->properties.preserveAlpha) + { + cm = QPainter::CompositionMode_SourceOver; + } + break; + default: //nothing + break; + } + targetImage->paste(&mTiledBuffer, cm); } QRect rect = mEditor->view()->mapCanvasToScreen(mTiledBuffer.bounds()).toRect(); @@ -1208,7 +1215,6 @@ void ScribbleArea::prepCanvas(int frame) o.fLayerVisibilityThreshold = mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD); o.scaling = mEditor->view()->scaling(); o.cmBufferBlendMode = mEditor->tools()->currentTool()->type() == ToolType::ERASER ? QPainter::CompositionMode_DestinationOut : QPainter::CompositionMode_SourceOver; - o.bIgnoreCanvasBuffer = currentTool()->type() == POLYLINE; OnionSkinPainterOptions onionSkinOptions; onionSkinOptions.enabledWhilePlaying = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK); @@ -1270,7 +1276,8 @@ void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter: void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA) { - mTiledBuffer.drawBrush(thePoint, brushWidth, mEditor->view()->mapScreenToCanvas(mCursorImg.rect()).width(), Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_SourceOver, useAA); + // We use Source as opposed to SourceOver here to avoid the dabs being added on top of each other + mTiledBuffer.drawBrush(thePoint, brushWidth, mEditor->view()->mapScreenToCanvas(mCursorImg.rect()).width(), Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_Source, useAA); } void ScribbleArea::drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity) diff --git a/core_lib/src/tool/erasertool.cpp b/core_lib/src/tool/erasertool.cpp index 405a6498c..7d57ade7e 100644 --- a/core_lib/src/tool/erasertool.cpp +++ b/core_lib/src/tool/erasertool.cpp @@ -200,7 +200,7 @@ void EraserTool::paintAt(QPointF point) brushWidth, properties.feather, QColor(255, 255, 255, 255), - QPainter::CompositionMode_DestinationOut, + QPainter::CompositionMode_SourceOver, opacity, properties.useFeather, properties.useAA == ON); @@ -240,7 +240,7 @@ void EraserTool::drawStroke() brushWidth, properties.feather, Qt::white, - QPainter::CompositionMode_DestinationOut, + QPainter::CompositionMode_SourceOver, opacity, properties.useFeather, properties.useAA == ON); From 3c9a7a4d4f7072ec577ff10e59b58bd882a5d209 Mon Sep 17 00:00:00 2001 From: scribblemaniac Date: Mon, 11 Dec 2023 23:41:36 -0700 Subject: [PATCH 7/7] Improve support for animated image importing (#1801) * Fix movie export not being a modal operation * Make DoubleProgressDialog inherit from QProgressDialog Probably not done initially because it adds a lot of member functions and properties that are not used by the double progress dialog. However it handles canceling much better than we do manually, including canceling with keyboard shortcuts or by closing the dialog. * Improve gif import progress dialog and refactor The progress dialog now updates as it imports the gif, and can be aborted during the import. Some refactoring was done to move some logic around. * Remove animated image handling from Editor::importBitmapImage The error checking has also been modified to hopefully be more robust as it no longer depends on reader.size() being invalid, which might not occur during certain types of image corruption, and may occur without corruption for certain types of image formats. * Add support for WebP import through a generic "Animated Image" import * Add non-animated WEBP import/export * Fix incorrect dialog titles --- app/src/actioncommands.cpp | 49 +++++++++++++- app/src/actioncommands.h | 1 + app/src/doubleprogressdialog.cpp | 4 +- app/src/doubleprogressdialog.h | 7 +- app/src/filedialog.cpp | 6 ++ app/src/importimageseqdialog.cpp | 10 ++- app/src/mainwindow2.cpp | 51 +-------------- app/src/mainwindow2.h | 2 +- app/ui/exportimageoptions.ui | 5 ++ app/ui/mainwindow2.ui | 6 +- core_lib/src/interface/editor.cpp | 105 +++++++++++++++++++++--------- core_lib/src/interface/editor.h | 4 +- core_lib/src/structure/object.cpp | 4 ++ core_lib/src/util/fileformat.cpp | 1 + core_lib/src/util/fileformat.h | 7 +- core_lib/src/util/filetype.h | 1 + 16 files changed, 166 insertions(+), 97 deletions(-) diff --git a/app/src/actioncommands.cpp b/app/src/actioncommands.cpp index 35727c69e..347779eeb 100644 --- a/app/src/actioncommands.cpp +++ b/app/src/actioncommands.cpp @@ -47,6 +47,8 @@ GNU General Public License for more details. #include "soundclip.h" #include "camera.h" +#include "importimageseqdialog.h" +#include "importpositiondialog.h" #include "movieimporter.h" #include "movieexporter.h" #include "filedialog.h" @@ -65,6 +67,50 @@ ActionCommands::ActionCommands(QWidget* parent) : QObject(parent) ActionCommands::~ActionCommands() {} +Status ActionCommands::importAnimatedImage() +{ + ImportImageSeqDialog fileDialog(mParent, ImportExportDialog::Import, FileType::ANIMATED_IMAGE); + fileDialog.exec(); + if (fileDialog.result() != QDialog::Accepted) + { + return Status::CANCELED; + } + int frameSpacing = fileDialog.getSpace(); + QString strImgFileLower = fileDialog.getFilePath(); + + ImportPositionDialog positionDialog(mEditor, mParent); + positionDialog.exec(); + if (positionDialog.result() != QDialog::Accepted) + { + return Status::CANCELED; + } + + // Show a progress dialog, as this could take a while if the gif is huge + QProgressDialog progressDialog(tr("Importing Animated Image..."), tr("Abort"), 0, 100, mParent); + hideQuestionMark(progressDialog); + progressDialog.setWindowModality(Qt::WindowModal); + progressDialog.show(); + + Status st = mEditor->importAnimatedImage(strImgFileLower, frameSpacing, [&progressDialog](int prog) { + progressDialog.setValue(prog); + QApplication::processEvents(); + }, [&progressDialog]() { + return progressDialog.wasCanceled(); + }); + + progressDialog.setValue(100); + progressDialog.close(); + + if (!st.ok()) + { + ErrorDialog errorDialog(st.title(), st.description(), st.details().html()); + errorDialog.exec(); + return Status::SAFE; + } + + return Status::OK; +} + Status ActionCommands::importMovieVideo() { QString filePath = FileDialog::getOpenFileName(mParent, FileType::MOVIE); @@ -106,6 +152,7 @@ Status ActionCommands::importMovieVideo() { ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent); errorDialog.exec(); + return Status::SAFE; } mEditor->layers()->notifyAnimationLengthChanged(); @@ -293,7 +340,7 @@ Status ActionCommands::exportMovie(bool isGif) desc.loop = dialog->getLoop(); desc.alpha = dialog->getTransparency(); - DoubleProgressDialog progressDlg; + DoubleProgressDialog progressDlg(mParent); progressDlg.setWindowModality(Qt::WindowModal); progressDlg.setWindowTitle(tr("Exporting movie")); Qt::WindowFlags eFlags = Qt::Dialog | Qt::WindowTitleHint; diff --git a/app/src/actioncommands.h b/app/src/actioncommands.h index f55e832c8..6d8ac3e34 100644 --- a/app/src/actioncommands.h +++ b/app/src/actioncommands.h @@ -37,6 +37,7 @@ class ActionCommands : public QObject void setCore(Editor* e) { mEditor = e; } // file + Status importAnimatedImage(); Status importMovieVideo(); Status importSound(FileType type); Status exportMovie(bool isGif = false); diff --git a/app/src/doubleprogressdialog.cpp b/app/src/doubleprogressdialog.cpp index 7d171a01b..22c2dc390 100644 --- a/app/src/doubleprogressdialog.cpp +++ b/app/src/doubleprogressdialog.cpp @@ -21,7 +21,7 @@ GNU General Public License for more details. #include DoubleProgressDialog::DoubleProgressDialog(QWidget *parent) : - QDialog(parent), + QProgressDialog(parent), ui(new Ui::DoubleProgressDialog) { ui->setupUi(this); @@ -29,7 +29,7 @@ DoubleProgressDialog::DoubleProgressDialog(QWidget *parent) : major = new ProgressBarControl(ui->majorProgressBar); minor = new ProgressBarControl(ui->minorProgressBar); - connect(ui->cancelButton, &QPushButton::pressed, this, &DoubleProgressDialog::canceled); + setCancelButton(ui->cancelButton); } DoubleProgressDialog::~DoubleProgressDialog() diff --git a/app/src/doubleprogressdialog.h b/app/src/doubleprogressdialog.h index 8dda8301f..93bfb67ab 100644 --- a/app/src/doubleprogressdialog.h +++ b/app/src/doubleprogressdialog.h @@ -18,14 +18,14 @@ GNU General Public License for more details. #ifndef DOUBLEPROGRESSDIALOG_H #define DOUBLEPROGRESSDIALOG_H -#include +#include #include namespace Ui { class DoubleProgressDialog; } -class DoubleProgressDialog : public QDialog +class DoubleProgressDialog : public QProgressDialog { Q_OBJECT @@ -63,9 +63,6 @@ class DoubleProgressDialog : public QDialog ProgressBarControl *major, *minor; -signals: - void canceled(); - private: Ui::DoubleProgressDialog *ui; }; diff --git a/app/src/filedialog.cpp b/app/src/filedialog.cpp index 8b35d69ca..42ee65db2 100644 --- a/app/src/filedialog.cpp +++ b/app/src/filedialog.cpp @@ -119,6 +119,7 @@ QString FileDialog::getDefaultExtensionByFileType(const FileType fileType) case FileType::IMAGE: return PFF_DEFAULT_IMAGE_EXT; case FileType::IMAGE_SEQUENCE: return PFF_DEFAULT_IMAGE_SEQ_EXT; case FileType::GIF: return PFF_DEFAULT_ANIMATED_EXT; + case FileType::ANIMATED_IMAGE: return PFF_DEFAULT_ANIMATED_EXT; case FileType::PALETTE: return PFF_DEFAULT_PALETTE_EXT; case FileType::MOVIE: return PFF_DEFAULT_MOVIE_EXT; case FileType::SOUND: return PFF_DEFAULT_SOUND_EXT; @@ -167,6 +168,7 @@ QString FileDialog::openDialogCaption(FileType fileType) case FileType::IMAGE: return tr("Import image"); case FileType::IMAGE_SEQUENCE: return tr("Import image sequence"); case FileType::GIF: return tr("Import Animated GIF"); + case FileType::ANIMATED_IMAGE: return tr("Import animated image"); case FileType::MOVIE: return tr("Import movie"); case FileType::SOUND: return tr("Import sound"); case FileType::PALETTE: return tr("Open palette"); @@ -182,6 +184,7 @@ QString FileDialog::saveDialogCaption(FileType fileType) case FileType::IMAGE: return tr("Export image"); case FileType::IMAGE_SEQUENCE: return tr("Export image sequence"); case FileType::GIF: return tr("Export Animated GIF"); + case FileType::ANIMATED_IMAGE: return tr("Export animated image"); case FileType::MOVIE: return tr("Export movie"); case FileType::SOUND: return tr("Export sound"); case FileType::PALETTE: return tr("Export palette"); @@ -197,6 +200,7 @@ QString FileDialog::openFileFilters(FileType fileType) case FileType::IMAGE: return PFF_IMAGE_FILTER; case FileType::IMAGE_SEQUENCE: return PFF_IMAGE_SEQ_FILTER; case FileType::GIF: return PFF_GIF_EXT_FILTER; + case FileType::ANIMATED_IMAGE: return PFF_ANIMATED_IMAGE_EXT_FILTER; case FileType::MOVIE: return PFF_MOVIE_EXT; case FileType::SOUND: return PFF_SOUND_EXT_FILTER; case FileType::PALETTE: return PFF_PALETTE_EXT_FILTER; @@ -212,6 +216,7 @@ QString FileDialog::saveFileFilters(FileType fileType) case FileType::IMAGE: return ""; case FileType::IMAGE_SEQUENCE: return ""; case FileType::GIF: return QString("%1 (*.gif)").arg(tr("Animated GIF")); + case FileType::ANIMATED_IMAGE: return ""; case FileType::MOVIE: return "MP4 (*.mp4);; AVI (*.avi);; WebM (*.webm);; APNG (*.apng)"; case FileType::SOUND: return ""; case FileType::PALETTE: return PFF_PALETTE_EXT_FILTER; @@ -286,6 +291,7 @@ QString FileDialog::toSettingKey(FileType fileType) case FileType::IMAGE: return "Image"; case FileType::IMAGE_SEQUENCE: return "ImageSequence"; case FileType::GIF: return "Animated GIF"; + case FileType::ANIMATED_IMAGE: return "Animated Image"; case FileType::MOVIE: return "Movie"; case FileType::SOUND: return "Sound"; case FileType::PALETTE: return "Palette"; diff --git a/app/src/importimageseqdialog.cpp b/app/src/importimageseqdialog.cpp index 896067a88..86c451591 100644 --- a/app/src/importimageseqdialog.cpp +++ b/app/src/importimageseqdialog.cpp @@ -58,10 +58,16 @@ void ImportImageSeqDialog::setupLayout() hideInstructionsLabel(true); - if (mFileType == FileType::GIF) { + switch (mFileType) + { + case FileType::GIF: setWindowTitle(tr("Import Animated GIF")); - } else { + break; + case FileType::IMAGE_SEQUENCE: setWindowTitle(tr("Import image sequence")); + break; + default: + setWindowTitle(tr("Import animated image")); } connect(uiOptionsBox->spaceSpinBox, static_cast(&QSpinBox::valueChanged), this, &ImportImageSeqDialog::setSpace); diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index 81ca1189b..da3d6ee2b 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -254,7 +254,7 @@ void MainWindow2::createMenus() connect(ui->actionImport_ImageSeqNum, &QAction::triggered, this, &MainWindow2::importPredefinedImageSet); connect(ui->actionImportLayers_from_pclx, &QAction::triggered, this, &MainWindow2::importLayers); connect(ui->actionImport_MovieVideo, &QAction::triggered, this, &MainWindow2::importMovieVideo); - connect(ui->actionImport_Gif, &QAction::triggered, this, &MainWindow2::importGIF); + connect(ui->actionImport_AnimatedImage, &QAction::triggered, this, &MainWindow2::importAnimatedImage); connect(ui->actionImport_Sound, &QAction::triggered, [=] { mCommands->importSound(FileType::SOUND); }); connect(ui->actionImport_MovieAudio, &QAction::triggered, [=] { mCommands->importSound(FileType::MOVIE); }); @@ -949,57 +949,12 @@ void MainWindow2::importLayers() importLayers->open(); } -void MainWindow2::importGIF() +void MainWindow2::importAnimatedImage() { - auto gifDialog = new ImportImageSeqDialog(this, ImportExportDialog::Import, FileType::GIF); - gifDialog->exec(); - if (gifDialog->result() == QDialog::Rejected) - { - return; - } - // Flag this so we don't prompt the user about auto-save in the middle of the import. mSuppressAutoSaveDialog = true; - ImportPositionDialog* positionDialog = new ImportPositionDialog(mEditor, this); - OnScopeExit(delete positionDialog) - - positionDialog->exec(); - if (positionDialog->result() != QDialog::Accepted) - { - return; - } - - int space = gifDialog->getSpace(); - - // Show a progress dialog, as this could take a while if the gif is huge - QProgressDialog progress(tr("Importing Animated GIF..."), tr("Abort"), 0, 100, this); - hideQuestionMark(progress); - progress.setWindowModality(Qt::WindowModal); - progress.show(); - - QString strImgFileLower = gifDialog->getFilePath(); - if (!strImgFileLower.toLower().endsWith(".gif")) - { - ErrorDialog errorDialog(tr("Import failed"), tr("You can only import files ending with .gif.")); - errorDialog.exec(); - } - else - { - Status st = mEditor->importGIF(strImgFileLower, space); - - progress.setValue(50); - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // Required to make progress bar update - - progress.setValue(100); - progress.close(); - - if (!st.ok()) - { - ErrorDialog errorDialog(st.title(), st.description(), st.details().html()); - errorDialog.exec(); - } - } + mCommands->importAnimatedImage(); mSuppressAutoSaveDialog = false; } diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h index a9f9f7a0c..9f1b683f6 100644 --- a/app/src/mainwindow2.h +++ b/app/src/mainwindow2.h @@ -90,7 +90,7 @@ public slots: void importPredefinedImageSet(); void importLayers(); void importMovieVideo(); - void importGIF(); + void importAnimatedImage(); void lockWidgets(bool shouldLock); diff --git a/app/ui/exportimageoptions.ui b/app/ui/exportimageoptions.ui index 23341ac93..98d1ae91c 100644 --- a/app/ui/exportimageoptions.ui +++ b/app/ui/exportimageoptions.ui @@ -98,6 +98,11 @@ TIFF + + + WEBP + + diff --git a/app/ui/mainwindow2.ui b/app/ui/mainwindow2.ui index 0d35271b7..d444be23e 100644 --- a/app/ui/mainwindow2.ui +++ b/app/ui/mainwindow2.ui @@ -65,7 +65,7 @@ - + @@ -912,9 +912,9 @@ F1 - + - Animated GIF... + Animated Image... diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index 7c4d5bc60..49af52acb 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -914,7 +914,7 @@ void Editor::updateObject() emit updateLayerCount(); } -Status Editor::importBitmapImage(const QString& filePath, int space) +Status Editor::importBitmapImage(const QString& filePath) { QImageReader reader(filePath); @@ -926,8 +926,7 @@ Status Editor::importBitmapImage(const QString& filePath, int space) dd << QString("Raw file path: %1").arg(filePath); QImage img(reader.size(), QImage::Format_ARGB32_Premultiplied); - if (img.isNull()) - { + if (!reader.read(&img)) { QString format = reader.format(); if (!format.isEmpty()) { @@ -955,33 +954,18 @@ Status Editor::importBitmapImage(const QString& filePath, int space) const QPoint pos(view()->getImportView().dx() - (img.width() / 2), view()->getImportView().dy() - (img.height() / 2)); - while (reader.read(&img)) + if (!layer->keyExists(mFrame)) { - int frameNumber = mFrame; - if (!layer->keyExists(frameNumber)) - { - addNewKey(); - } - BitmapImage* bitmapImage = layer->getBitmapImageAtFrame(frameNumber); - BitmapImage importedBitmapImage(pos, img); - bitmapImage->paste(&importedBitmapImage); - emit frameModified(bitmapImage->pos()); + addNewKey(); + } + BitmapImage* bitmapImage = layer->getBitmapImageAtFrame(mFrame); + BitmapImage importedBitmapImage(pos, img); + bitmapImage->paste(&importedBitmapImage); + emit frameModified(bitmapImage->pos()); - if (space > 1) { - frameNumber += space; - } else { - frameNumber += 1; - } - scrubTo(frameNumber); + scrubTo(mFrame+1); - backup(tr("Import Image")); - - // Workaround for tiff import getting stuck in this loop - if (!reader.supportsAnimation()) - { - break; - } - } + backup(tr("Import Image")); return status; } @@ -1048,17 +1032,76 @@ Status Editor::importImage(const QString& filePath) } } -Status Editor::importGIF(const QString& filePath, int numOfImages) +Status Editor::importAnimatedImage(const QString& filePath, int frameSpacing, const std::function& progressChanged, const std::function& wasCanceled) { + frameSpacing = qMax(1, frameSpacing); + + DebugDetails dd; + dd << QString("Raw file path: %1").arg(filePath); + Layer* layer = layers()->currentLayer(); if (layer->type() != Layer::BITMAP) { - DebugDetails dd; - dd << QString("Raw file path: %1").arg(filePath); dd << QString("Current layer: %1").arg(layer->type()); return Status(Status::ERROR_INVALID_LAYER_TYPE, dd, tr("Import failed"), tr("You can only import images to a bitmap layer.")); } - return importBitmapImage(filePath, numOfImages); + LayerBitmap* bitmapLayer = static_cast(layers()->currentLayer()); + + QImageReader reader(filePath); + dd << QString("QImageReader format: %1").arg(QString(reader.format())); + if (!reader.supportsAnimation()) { + return Status(Status::ERROR_INVALID_LAYER_TYPE, dd, tr("Import failed"), tr("The selected image has a format that does not support animation.")); + } + + QImage img(reader.size(), QImage::Format_ARGB32_Premultiplied); + const QPoint pos(view()->getImportView().dx() - (img.width() / 2), + view()->getImportView().dy() - (img.height() / 2)); + int totalFrames = reader.imageCount(); + while (reader.read(&img)) + { + if (reader.error()) + { + dd << QString("QImageReader ImageReaderError type: %1").arg(reader.errorString()); + + QString errorDesc; + switch(reader.error()) + { + case QImageReader::ImageReaderError::FileNotFoundError: + errorDesc = tr("File not found at path \"%1\". Please check the image is present at the specified location and try again.").arg(filePath); + break; + case QImageReader::UnsupportedFormatError: + errorDesc = tr("Image format is not supported. Please convert the image file to one of the following formats and try again:\n%1") + .arg((QString)reader.supportedImageFormats().join(", ")); + break; + default: + errorDesc = tr("An error has occurred while reading the image. Please check that the file is a valid image and try again."); + } + + return Status(Status::FAIL, dd, tr("Import failed"), errorDesc); + } + + if (!bitmapLayer->keyExists(mFrame)) + { + addNewKey(); + } + BitmapImage* bitmapImage = bitmapLayer->getBitmapImageAtFrame(mFrame); + BitmapImage importedBitmapImage(pos, img); + bitmapImage->paste(&importedBitmapImage); + emit frameModified(bitmapImage->pos()); + + if (wasCanceled()) + { + break; + } + + scrubTo(mFrame + frameSpacing); + + backup(tr("Import Image")); + + progressChanged(qFloor(qMin(static_cast(reader.currentImageNumber()) / totalFrames, 1.0) * 100)); + } + + return Status::OK; } void Editor::selectAll() const diff --git a/core_lib/src/interface/editor.h b/core_lib/src/interface/editor.h index eef05b266..a3ba0e179 100644 --- a/core_lib/src/interface/editor.h +++ b/core_lib/src/interface/editor.h @@ -174,7 +174,7 @@ class Editor : public QObject void clearCurrentFrame(); Status importImage(const QString& filePath); - Status importGIF(const QString& filePath, int numOfImages = 0); + Status importAnimatedImage(const QString& filePath, int frameSpacing, const std::function& progressChanged, const std::function& wasCanceled); void restoreKey(); void scrubNextKeyFrame(); @@ -230,7 +230,7 @@ class Editor : public QObject void resetAutoSaveCounter(); private: - Status importBitmapImage(const QString&, int space = 0); + Status importBitmapImage(const QString&); Status importVectorImage(const QString&); void pasteToCanvas(BitmapImage* bitmapImage, int frameNumber); diff --git a/core_lib/src/structure/object.cpp b/core_lib/src/structure/object.cpp index 17834e6f7..bb25cdb1e 100644 --- a/core_lib/src/structure/object.cpp +++ b/core_lib/src/structure/object.cpp @@ -828,6 +828,10 @@ bool Object::exportFrames(int frameStart, int frameEnd, extension = ".bmp"; transparency = false; } + if (formatStr == "WEBP" || formatStr == "webp") { + format = "WEBP"; + extension = ".webp"; + } if (filePath.endsWith(extension, Qt::CaseInsensitive)) { filePath.chop(extension.size()); diff --git a/core_lib/src/util/fileformat.cpp b/core_lib/src/util/fileformat.cpp index 55707cfa5..3d4eb7707 100644 --- a/core_lib/src/util/fileformat.cpp +++ b/core_lib/src/util/fileformat.cpp @@ -59,6 +59,7 @@ QString detectFormatByFileNameExtension(const QString& fileName) { "tif", "TIF" }, { "tiff", "TIF" }, { "bmp", "BMP" }, + { "webp", "WEBP" }, { "mp4", "MP4" }, { "avi", "AVI" }, { "gif", "GIF" }, diff --git a/core_lib/src/util/fileformat.h b/core_lib/src/util/fileformat.h index 2ce07e5fa..6935d0aa4 100644 --- a/core_lib/src/util/fileformat.h +++ b/core_lib/src/util/fileformat.h @@ -40,10 +40,10 @@ GNU General Public License for more details. ";;SWF(*.swf);;FLV(*.flv);;WEBM(*.webm);;WMV(*.wmv)" #define PFF_IMAGE_FILTER \ - QCoreApplication::translate("FileFormat", "Image formats") + " (*.png *.jpg *.jpeg *.bmp *.tif *.tiff);;PNG (*.png);;JPG(*.jpg *.jpeg);;BMP(*.bmp);;TIFF(*.tif *.tiff)" + QCoreApplication::translate("FileFormat", "Image formats") + " (*.png *.jpg *.jpeg *.bmp *.tif *.tiff *.webp);;PNG (*.png);;JPG(*.jpg *.jpeg);;BMP(*.bmp);;TIFF(*.tif *.tiff);;WEBP(*.webp)" #define PFF_IMAGE_SEQ_FILTER \ - QCoreApplication::translate("FileFormat", "Image formats") + " (*.png *.jpg *.jpeg *.bmp *.tif *.tiff);;PNG (*.png);;JPG(*.jpg *.jpeg);;BMP(*.bmp);;TIFF(*.tif *.tiff)" + QCoreApplication::translate("FileFormat", "Image formats") + " (*.png *.jpg *.jpeg *.bmp *.tif *.tiff *.webp);;PNG (*.png);;JPG(*.jpg *.jpeg);;BMP(*.bmp);;TIFF(*.tif *.tiff);;WEBP(*.webp)" #define PFF_PALETTE_EXT_FILTER \ QCoreApplication::translate("FileFormat", "Palette formats") + " (*.xml *.gpl);;" + QCoreApplication::translate("FileFormat", "Pencil2D Palette") + " (*.xml);;" + QCoreApplication::translate("FileFormat", "GIMP Palette") + " (*.gpl)" @@ -51,6 +51,9 @@ GNU General Public License for more details. #define PFF_GIF_EXT_FILTER \ QCoreApplication::translate("FileFormat", "Animated GIF") + " (*.gif)" +#define PFF_ANIMATED_IMAGE_EXT_FILTER \ + QCoreApplication::translate("FileFormat", "Animated image formats") + " (*.gif *.webp);;GIF(*.gif);;WEBP(*.webp)" + #define PFF_SOUND_EXT_FILTER \ QCoreApplication::translate("FileFormat", "Sound formats") + " (*.wav *.mp3 *.wma *.ogg *.flac *.opus *.aiff *.aac *.caf);;WAV (*.wav);;MP3 (*.mp3);;WMA (*.wma);;OGG (*.ogg);;FLAC (*.flac);;Opus (*.opus);;AIFF (*.aiff);;AAC (*.aac);;CAF (*.caf)" diff --git a/core_lib/src/util/filetype.h b/core_lib/src/util/filetype.h index a0558fa5a..9a88fad1e 100644 --- a/core_lib/src/util/filetype.h +++ b/core_lib/src/util/filetype.h @@ -7,6 +7,7 @@ enum class FileType IMAGE, IMAGE_SEQUENCE, GIF, + ANIMATED_IMAGE, MOVIE, SOUND, PALETTE