diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index d493374cdc..67a7bd453f 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -105,9 +105,13 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../ ./libpag) add_executable(PAGViewer ${RC_FILES} ${PAG_VIEWER_SOURCE_FILES} ${QT_RESOURCES}) if (APPLE) list(APPEND PAG_VIEWER_INCLUDES ../third_party/out/rttr/mac/include) + list(APPEND PAG_VIEWER_INCLUDES ../third_party/tgfx/third_party/out/zlib/mac/include) + list(APPEND PAG_VIEWER_INCLUDES ../third_party/tgfx/third_party/out/libpng/mac/include) list(APPEND VIEWER_VENDOR_LIBRARIES ${CMAKE_SOURCE_DIR}/../vendor/sparkle/mac/Sparkle.framework) elseif (WIN32) list(APPEND PAG_VIEWER_INCLUDES ../third_party/out/rttr/win/include) + list(APPEND PAG_VIEWER_INCLUDES ../third_party/tgfx/third_party/out/zlib/win/include) + list(APPEND PAG_VIEWER_INCLUDES ../third_party/tgfx/third_party/out/libpng/win/include) list(APPEND PAG_VIEWER_INCLUDES ../vendor/winsparkle/include) list(APPEND VIEWER_VENDOR_LIBRARIES ${CMAKE_SOURCE_DIR}/../vendor/winsparkle/win/x64/${CMAKE_BUILD_TYPE}/WinSparkle.lib) endif () diff --git a/viewer/qml/Main.qml b/viewer/qml/Main.qml index f23c3f5206..8771eebafb 100644 --- a/viewer/qml/Main.qml +++ b/viewer/qml/Main.qml @@ -2,7 +2,9 @@ import PAG import QtCore import QtQuick import QtQuick.Dialogs +import QtQuick.Controls import Qt.labs.settings +import Qt.labs.platform as Platform import "components" import "utils" @@ -127,18 +129,6 @@ PAGWindow { } } - FileDialog { - id: openPAGFileDialog - visible: false - title: qsTr("Open PAG File") - fileMode: FileDialog.OpenFile - nameFilters: ["PAG files(*.pag)"] - onAccepted: { - let filePath = openPAGFileDialog.selectedFile; - mainForm.pagView.setFile(filePath); - } - } - SettingsWindow { id: settingsWindow visible: false @@ -163,6 +153,120 @@ PAGWindow { aboutMessage: "PAGViewer " + Qt.application.version + "

Copyright © 2017-present Tencent. All rights reserved." } + PAGTaskFactory { + id: taskFactory + objectName: "taskFactory" + } + + FileDialog { + id: openFileDialog + + property var currentAcceptHandler: null + + visible: false + title: "" + fileMode: FileDialog.OpenFile + nameFilters: [] + } + + Platform.FolderDialog { + id: openFolderDialog + + property var currentAcceptHandler: null + + visible: false + title: qsTr("Select Save Path") + } + + PAGWindow { + id: progressWindow + + property var task + property alias progressBar: progressBar + + width: 300 + height: 64 + minimumWidth: width + maximumWidth: width + minimumHeight: height + maximumHeight: height + hasMenu: false + canResize: false + titleBarHeight: windowTitleBarHeight + visible: false + + PAGRectangle { + id: rectangle + + color: "#2D2D37" + anchors.fill: parent + leftTopRadius: false + rightTopRadius: false + radius: 5 + + ProgressBar { + id: progressBar + width: parent.width - 24 + height: 30 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + value: 0 + + contentItem: Item { + Rectangle { + width: parent.width + height: 15 + radius: 5 + color: "#DDDDDD" + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: progressBar.visualPosition * parent.width + height: 15 + radius: 5 + color: "#448EF9" + anchors.verticalCenter: parent.verticalCenter + } + + Text { + anchors.centerIn: parent + text: Math.round(progressBar.value * 100) + "%" + color: progressBar.value > 0.5 ? "white" : "black" + font.pixelSize: 12 + } + } + } + } + + onClosing: { + if (task) { + task.stop(); + } + } + } + + Connections { + id: taskConnections + onProgressChanged: function (progress) { + progressWindow.progressBar.value = progress; + } + + onVisibleChanged: function (visible) { + progressWindow.visible = visible; + } + + onTaskFinished: function (filePath, result) { + if (result !== 0) { + let errStr = qsTr("Export failed, error code: "); + alert(errStr + result); + } + progressWindow.task = null; + progressWindow.progressBar.value = 0; + progressWindow.visible = false; + } + } + Component.onCompleted: { viewWindow.title = "PAGViewer"; @@ -217,11 +321,19 @@ PAGWindow { case "open-pag-file": if (mainForm.hasPAGFile) { let filePath = mainForm.pagView.filePath; - openPAGFileDialog.currentFolder = Utils.getFileDir(filePath); + openFileDialog.currentFolder = Utils.getFileDir(filePath); } else { - openPAGFileDialog.currentFolder = StandardPaths.writableLocation(StandardPaths.DocumentsLocation); + openFileDialog.currentFolder = StandardPaths.writableLocation(StandardPaths.DocumentsLocation); } - openPAGFileDialog.open(); + openFileDialog.accepted.disconnect(); + openFileDialog.fileMode = FileDialog.OpenFile; + openFileDialog.title = qsTr("Open PAG File"); + openFileDialog.nameFilters = ["PAG files(*.pag)"]; + openFileDialog.accepted.connect(function () { + let filePath = openFileDialog.selectedFile; + mainForm.pagView.setFile(filePath); + }); + openFileDialog.open(); break; case "close-window": viewWindow.close(); @@ -273,6 +385,79 @@ PAGWindow { case "fullscreen-window": viewWindow.visibility = viewWindow.visibility !== Window.Maximized ? Window.Maximized : Window.AutomaticVisibility; break; + case "export-frame-as-png": + if (openFileDialog.currentAcceptHandler) { + openFileDialog.accepted.disconnect(openFileDialog.currentAcceptHandler); + } + openFileDialog.fileMode = FileDialog.SaveFile; + openFileDialog.title = qsTr("Select save path"); + openFileDialog.nameFilters = ["PNG files(*.png)"]; + openFileDialog.defaultSuffix = "png"; + openFileDialog.currentFolder = Utils.getFileDir(mainForm.pagView.filePath); + openFileDialog.currentAcceptHandler = function () { + let filePath = openFileDialog.selectedFile; + let task = taskFactory.createTask(PAGTaskFactory.PAGTaskType_ExportPNG, filePath, { + "exportFrame": mainForm.pagView.currentFrame + }); + if (task) { + taskConnections.target = task; + progressWindow.title = qsTr("Exporting"); + progressWindow.task = task; + progressWindow.visible = true; + progressWindow.raise(); + task.start(); + } + }; + openFileDialog.accepted.connect(openFileDialog.currentAcceptHandler); + openFileDialog.open(); + break; + case "export-as-png-sequence": + if (openFolderDialog.currentAcceptHandler) { + openFolderDialog.accepted.disconnect(openFolderDialog.currentAcceptHandler); + } + openFolderDialog.title = qsTr("Select save path"); + openFolderDialog.currentFolder = Utils.getFileDir(mainForm.pagView.filePath); + openFolderDialog.currentAcceptHandler = function () { + let filePath = openFolderDialog.folder; + let task = taskFactory.createTask(PAGTaskFactory.PAGTaskType_ExportPNG, filePath, {}); + if (task) { + taskConnections.target = task; + progressWindow.title = qsTr("Exporting"); + progressWindow.progressBar.value = 0; + progressWindow.task = task; + progressWindow.visible = true; + progressWindow.raise(); + task.start(); + } + }; + openFolderDialog.accepted.connect(openFolderDialog.currentAcceptHandler); + openFolderDialog.open(); + break; + case "export-as-apng": + if (openFileDialog.currentAcceptHandler) { + openFileDialog.accepted.disconnect(openFileDialog.currentAcceptHandler); + } + openFileDialog.fileMode = FileDialog.SaveFile; + openFileDialog.title = qsTr("Select save path"); + openFileDialog.nameFilters = ["APNG files(*.png)"]; + openFileDialog.defaultSuffix = "png"; + openFileDialog.currentFolder = Utils.getFileDir(mainForm.pagView.filePath); + openFileDialog.currentAcceptHandler = function () { + let filePath = openFileDialog.selectedFile; + let task = taskFactory.createTask(PAGTaskFactory.PAGTaskType_ExportAPNG, filePath, {}); + if (task) { + taskConnections.target = task; + progressWindow.title = qsTr("Exporting"); + progressWindow.progressBar.value = 0; + progressWindow.task = task; + progressWindow.visible = true; + progressWindow.raise(); + task.start(); + } + }; + openFileDialog.accepted.connect(openFileDialog.currentAcceptHandler); + openFileDialog.open(); + break; default: console.log(`Undefined command: [${command}]`); break; diff --git a/viewer/qml/Menu.qml b/viewer/qml/Menu.qml index 2ae63e4065..a0589d63cf 100644 --- a/viewer/qml/Menu.qml +++ b/viewer/qml/Menu.qml @@ -48,6 +48,32 @@ Item { root.command("open-preferences"); } } + PAGMenu { + menuWidth: windowsMenuBar.menuWidth + title: qsTr("Export") + Action { + text: qsTr("Export as PNG Sequence Frames") + enabled: root.hasPAGFile + onTriggered: { + root.command('export-as-png-sequence'); + } + } + Action { + text: qsTr("Export as APNG") + enabled: root.hasPAGFile + onTriggered: { + root.command('export-as-apng'); + } + } + Action { + text: qsTr("Export current frame as PNG") + enabled: root.hasPAGFile + shortcut: "Ctrl+P" + onTriggered: { + root.command('export-frame-as-png'); + } + } + } } PAGMenu { @@ -196,6 +222,31 @@ Item { root.command("open-pag-file"); } } + Platform.Menu { + title: qsTr("Export") + Platform.MenuItem { + text: qsTr("Export as PNG Sequence Frames") + enabled: root.hasPAGFile + onTriggered: { + root.command('export-as-png-sequence'); + } + } + Platform.MenuItem { + text: qsTr("Export as APNG") + enabled: root.hasPAGFile + onTriggered: { + root.command('export-as-apng'); + } + } + Platform.MenuItem { + text: qsTr("Export current frame as PNG") + enabled: root.hasPAGFile + shortcut: "Meta+P" + onTriggered: { + root.command('export-frame-as-png'); + } + } + } } Platform.Menu { title: qsTr("Play") diff --git a/viewer/qml/utils/Utils.qml b/viewer/qml/utils/Utils.qml index 0a6ed3d424..2456705cd9 100644 --- a/viewer/qml/utils/Utils.qml +++ b/viewer/qml/utils/Utils.qml @@ -23,4 +23,13 @@ QtObject { let fileName = urlObject.pathname.split('/').pop(); return fileName; } -} \ No newline at end of file + + function replaceLastIndexOf(str, oldPara, newPara) { + let lastIndex = str.lastIndexOf(oldPara); + if (lastIndex !== -1) { + return str; + } + + return str.substring(0, lastIndex) + newPara + str.substring(lastIndex + oldPara.length); + } +} diff --git a/viewer/src/main.cpp b/viewer/src/main.cpp index 66cde5f1f2..137aa2612c 100644 --- a/viewer/src/main.cpp +++ b/viewer/src/main.cpp @@ -22,6 +22,7 @@ #include #include "PAGViewer.h" #include "rendering/PAGView.h" +#include "task/PAGTaskFactory.h" int main(int argc, char* argv[]) { bool cpuMode = false; @@ -60,6 +61,7 @@ int main(int argc, char* argv[]) { pag::PAGViewer app(argc, argv); QApplication::setWindowIcon(QIcon(":/images/window-icon.png")); qmlRegisterType("PAG", 1, 0, "PAGView"); + qmlRegisterType("PAG", 1, 0, "PAGTaskFactory"); app.openFile(filePath.data()); return QApplication::exec(); diff --git a/viewer/src/platform/mac/PAGUtilsImpl.h b/viewer/src/platform/mac/PAGUtilsImpl.h new file mode 100644 index 0000000000..73ea82fce2 --- /dev/null +++ b/viewer/src/platform/mac/PAGUtilsImpl.h @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace pag::Utils { + +auto openFileInFinder(QFileInfo& fileInfo) -> void; + +} // namespace pag::Utils diff --git a/viewer/src/platform/mac/PAGUtilsImpl.mm b/viewer/src/platform/mac/PAGUtilsImpl.mm new file mode 100644 index 0000000000..6fbeea2dd8 --- /dev/null +++ b/viewer/src/platform/mac/PAGUtilsImpl.mm @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PAGUtilsImpl.h" +#import + +namespace pag::Utils { + +auto openFileInFinder(QFileInfo& fileInfo) -> void { + if (!fileInfo.exists()) { + return; + } + @autoreleasepool { + NSString* filePath = + [NSString stringWithUTF8String:fileInfo.absoluteFilePath().toUtf8().constData()]; + NSURL* fileUrl = [NSURL fileURLWithPath:filePath]; + if (!fileUrl) { + return; + } + fileUrl = [fileUrl URLByStandardizingPath]; + [[NSWorkspace sharedWorkspace] selectFile:filePath + inFileViewerRootedAtPath:[[fileUrl URLByDeletingLastPathComponent] path]]; + } +} + +} // namespace pag::Utils \ No newline at end of file diff --git a/viewer/src/platform/mac/PAGWindowHelper.h b/viewer/src/platform/mac/PAGWindowHelper.h index 856445451a..04766d6ac0 100644 --- a/viewer/src/platform/mac/PAGWindowHelper.h +++ b/viewer/src/platform/mac/PAGWindowHelper.h @@ -1,3 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + #pragma once #include diff --git a/viewer/src/platform/mac/PAGWindowHelper.mm b/viewer/src/platform/mac/PAGWindowHelper.mm index fdc68915fb..ddc761ec0d 100644 --- a/viewer/src/platform/mac/PAGWindowHelper.mm +++ b/viewer/src/platform/mac/PAGWindowHelper.mm @@ -1,3 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + #include "PAGWindowHelper.h" #include diff --git a/viewer/src/platform/win/PAGUtilsImpl.cpp b/viewer/src/platform/win/PAGUtilsImpl.cpp new file mode 100644 index 0000000000..4e17ecab42 --- /dev/null +++ b/viewer/src/platform/win/PAGUtilsImpl.cpp @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PAGUtilsImpl.h" +#include +#include +#include + +namespace pag::Utils { + +auto openFileInFinder(QFileInfo& fileInfo) -> void { + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + return; + } + + QString folderPath = QDir::toNativeSeparators(fileInfo.absolutePath()); + QString filePath = QDir::toNativeSeparators(fileInfo.absoluteFilePath()); + + std::wstring folderWPath = folderPath.toStdWString(); + std::wstring fileWPath = filePath.toStdWString(); + + PIDLIST_ABSOLUTE pidlFolder(ILCreateFromPathW(folderWPath.c_str())); + PIDLIST_ABSOLUTE pidlFile(ILCreateFromPathW(fileWPath.c_str())); + + if (!pidlFolder || !pidlFile) { + if (pidlFolder) { + ILFree(pidlFolder); + } + if (pidlFile) { + ILFree(pidlFile); + } + CoUninitialize(); + return; + } + + PCUITEMID_CHILD pidlChild = ILFindChild(pidlFolder, pidlFile); + if (!pidlChild) { + ILFree(pidlFolder); + ILFree(pidlFile); + CoUninitialize(); + return; + } + + PCUITEMID_CHILD_ARRAY pidls = const_cast(&pidlChild); + SHOpenFolderAndSelectItems(pidlFolder, 1, pidls, 0); + + ILFree(pidlFolder); + ILFree(pidlFile); + CoUninitialize(); +} + +} // namespace pag::Utils \ No newline at end of file diff --git a/viewer/src/platform/win/PAGUtilsImpl.h b/viewer/src/platform/win/PAGUtilsImpl.h new file mode 100644 index 0000000000..73ea82fce2 --- /dev/null +++ b/viewer/src/platform/win/PAGUtilsImpl.h @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace pag::Utils { + +auto openFileInFinder(QFileInfo& fileInfo) -> void; + +} // namespace pag::Utils diff --git a/viewer/src/platform/win/PAGWindowHelper.cpp b/viewer/src/platform/win/PAGWindowHelper.cpp index d2443f123e..245227498e 100644 --- a/viewer/src/platform/win/PAGWindowHelper.cpp +++ b/viewer/src/platform/win/PAGWindowHelper.cpp @@ -1,3 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + #include "PAGWindowHelper.h" namespace pag { diff --git a/viewer/src/platform/win/PAGWindowHelper.h b/viewer/src/platform/win/PAGWindowHelper.h index 856445451a..04766d6ac0 100644 --- a/viewer/src/platform/win/PAGWindowHelper.h +++ b/viewer/src/platform/win/PAGWindowHelper.h @@ -1,3 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + #pragma once #include diff --git a/viewer/src/rendering/PAGRenderThread.cpp b/viewer/src/rendering/PAGRenderThread.cpp index 880617bde4..15356ea586 100644 --- a/viewer/src/rendering/PAGRenderThread.cpp +++ b/viewer/src/rendering/PAGRenderThread.cpp @@ -18,6 +18,7 @@ #include "PAGRenderThread.h" #include +#include "rendering/PAGView.h" namespace pag { diff --git a/viewer/src/rendering/PAGRenderThread.h b/viewer/src/rendering/PAGRenderThread.h index fbdbc6f66e..f0cf51e2b1 100644 --- a/viewer/src/rendering/PAGRenderThread.h +++ b/viewer/src/rendering/PAGRenderThread.h @@ -19,10 +19,11 @@ #pragma once #include -#include "rendering/PAGView.h" namespace pag { +class PAGView; + class PAGRenderThread : public QThread { Q_OBJECT public: diff --git a/viewer/src/rendering/PAGView.cpp b/viewer/src/rendering/PAGView.cpp index 900759b1b6..969adeadaa 100644 --- a/viewer/src/rendering/PAGView.cpp +++ b/viewer/src/rendering/PAGView.cpp @@ -19,7 +19,6 @@ #include "PAGView.h" #include #include "pag/file.h" -#include "rendering/PAGRenderThread.h" #include "tgfx/core/Clock.h" namespace pag { @@ -27,19 +26,17 @@ namespace pag { PAGView::PAGView(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); drawable = GPUDrawable::MakeFrom(this); - pagPlayer = new PAGPlayer(); + pagPlayer = std::make_unique(); auto pagSurface = PAGSurface::MakeFrom(drawable); pagPlayer->setSurface(pagSurface); - renderThread = new PAGRenderThread(this); - renderThread->moveToThread(renderThread); - drawable->moveToThread(renderThread); + renderThread = std::make_unique(this); + renderThread->moveToThread(renderThread.get()); + drawable->moveToThread(renderThread.get()); } PAGView::~PAGView() { - QMetaObject::invokeMethod(renderThread, "shutDown", Qt::QueuedConnection); + QMetaObject::invokeMethod(renderThread.get(), "shutDown", Qt::QueuedConnection); renderThread->wait(); - delete renderThread; - delete pagPlayer; } auto PAGView::getPAGWidth() const -> int { @@ -149,7 +146,7 @@ auto PAGView::setIsPlaying(bool isPlaying) -> void { Q_EMIT isPlayingChanged(isPlaying); if (isPlaying) { lastPlayTime = tgfx::Clock::Now(); - QMetaObject::invokeMethod(renderThread, "flush", Qt::QueuedConnection); + QMetaObject::invokeMethod(renderThread.get(), "flush", Qt::QueuedConnection); } } @@ -167,7 +164,7 @@ auto PAGView::setProgress(double progress) -> void { pagPlayer->setProgress(progress); this->progress = progress; Q_EMIT progressChanged(progress); - QMetaObject::invokeMethod(renderThread, "flush", Qt::QueuedConnection); + QMetaObject::invokeMethod(renderThread.get(), "flush", Qt::QueuedConnection); } auto PAGView::setFile(const QString& filePath) -> bool { @@ -277,7 +274,7 @@ QSGNode* PAGView::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { } setProgress(progress); } - QMetaObject::invokeMethod(renderThread, "flush", Qt::QueuedConnection); + QMetaObject::invokeMethod(renderThread.get(), "flush", Qt::QueuedConnection); } return node; } diff --git a/viewer/src/rendering/PAGView.h b/viewer/src/rendering/PAGView.h index 1d5f9b77df..91cc00b0b5 100644 --- a/viewer/src/rendering/PAGView.h +++ b/viewer/src/rendering/PAGView.h @@ -22,11 +22,10 @@ #include #include "pag/pag.h" #include "platform/qt/GPUDrawable.h" +#include "rendering/PAGRenderThread.h" namespace pag { -class PAGRenderThread; - class PAGView : public QQuickItem { Q_OBJECT public: @@ -63,7 +62,8 @@ class PAGView : public QQuickItem { Q_SIGNAL void isPlayingChanged(bool isPlaying); Q_SIGNAL void progressChanged(double progress); - Q_SIGNAL void fileChanged(std::shared_ptr pagFile, std::string filePath); + Q_SIGNAL void fileChanged(const std::shared_ptr& pagFile, + const std::string& filePath); Q_INVOKABLE bool setFile(const QString& filePath); Q_INVOKABLE void firstFrame(); @@ -82,8 +82,8 @@ class PAGView : public QQuickItem { double progress = 0.0; double progressPerFrame = 0.0; QString filePath = ""; - PAGPlayer* pagPlayer = nullptr; - PAGRenderThread* renderThread = nullptr; + std::unique_ptr pagPlayer = nullptr; + std::unique_ptr renderThread = nullptr; std::shared_ptr pagFile = nullptr; std::shared_ptr drawable = nullptr; diff --git a/viewer/src/rendering/PAGWindow.cpp b/viewer/src/rendering/PAGWindow.cpp index 7e1d91c255..d62c2cbd37 100644 --- a/viewer/src/rendering/PAGWindow.cpp +++ b/viewer/src/rendering/PAGWindow.cpp @@ -20,6 +20,8 @@ #include #include #include "PAGWindowHelper.h" +#include "task/PAGTaskFactory.h" + namespace pag { QList PAGWindow::AllWindows; @@ -27,10 +29,7 @@ QList PAGWindow::AllWindows; PAGWindow::PAGWindow(QObject* parent) : QObject(parent) { } -PAGWindow::~PAGWindow() { - delete engine; - delete windowHelper; -} +PAGWindow::~PAGWindow() = default; auto PAGWindow::openFile(QString path) -> void { bool result = pagView->setFile(path); @@ -47,11 +46,11 @@ auto PAGWindow::onPAGViewerDestroyed() -> void { } auto PAGWindow::open() -> void { - engine = new QQmlApplicationEngine; - windowHelper = new PAGWindowHelper(); + engine = std::make_unique(); + windowHelper = std::make_unique(); auto context = engine->rootContext(); - context->setContextProperty("windowHelper", windowHelper); + context->setContextProperty("windowHelper", windowHelper.get()); engine->load(QUrl(QStringLiteral("qrc:/qml/Main.qml"))); @@ -61,9 +60,11 @@ auto PAGWindow::open() -> void { window->setTextRenderType(QQuickWindow::TextRenderType::NativeTextRendering); pagView = window->findChild("pagView"); + auto* taskFactory = window->findChild("taskFactory"); connect(window, SIGNAL(closing(QQuickCloseEvent*)), this, SLOT(onPAGViewerDestroyed()), Qt::QueuedConnection); + connect(pagView, &PAGView::fileChanged, taskFactory, &PAGTaskFactory::resetFile); } auto PAGWindow::getFilePath() -> QString { diff --git a/viewer/src/rendering/PAGWindow.h b/viewer/src/rendering/PAGWindow.h index e1659a69cd..ec28d0d505 100644 --- a/viewer/src/rendering/PAGWindow.h +++ b/viewer/src/rendering/PAGWindow.h @@ -41,12 +41,11 @@ class PAGWindow : public QObject { static QList AllWindows; private: - QString filePath; + QString filePath = ""; QQuickWindow* window = nullptr; PAGView* pagView = nullptr; - QThread* renderThread = nullptr; - PAGWindowHelper* windowHelper = nullptr; - QQmlApplicationEngine* engine = nullptr; + std::unique_ptr windowHelper = nullptr; + std::unique_ptr engine = nullptr; }; } // namespace pag diff --git a/viewer/src/task/PAGTask.cpp b/viewer/src/task/PAGTask.cpp new file mode 100644 index 0000000000..7b30fe2697 --- /dev/null +++ b/viewer/src/task/PAGTask.cpp @@ -0,0 +1,152 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PAGTask.h" +#include "base/utils/TimeUtil.h" +#include "utils/PAGFileUtils.h" + +namespace pag { + +PAGTask::PAGTask() : QObject(nullptr) { +} + +auto PAGTask::getVisible() const -> bool { + return visible; +} + +auto PAGTask::getProgress() const -> double { + return progress; +} + +PAGPlayTask::PAGPlayTask(std::shared_ptr& pagFile, const QString& filePath) + : filePath(filePath), pagFile(pagFile) { + QObject::connect(&workerThread, &QThread::started, this, &PAGPlayTask::startInternal, + Qt::DirectConnection); +} + +PAGPlayTask::~PAGPlayTask() { + if (workerThread.isRunning()) { + workerThread.quit(); + } + QObject::disconnect(&workerThread, &QThread::started, this, &PAGPlayTask::startInternal); + releaseResource(); +} + +auto PAGPlayTask::start() -> void { + if (pagFile == nullptr) { + return; + } + isWorking = true; + onBegin(); + workerThread.start(); +} + +auto PAGPlayTask::stop() -> void { + this->isWorking = false; + currentFrame = 0; +} + +auto PAGPlayTask::onBegin() -> void { + initOpenGLEnvironment(); + visible = true; + Q_EMIT visibleChanged(visible); +} + +auto PAGPlayTask::onFinish() -> int { + visible = false; + Q_EMIT visibleChanged(visible); + return 0; +} + +auto PAGPlayTask::onFrameFlush(double progress) -> void { + this->progress = progress; + Q_EMIT progressChanged(progress); +} + +auto PAGPlayTask::isNeedRenderCurrentFrame() -> bool { + return currentFrame >= 0; +} + +auto PAGPlayTask::releaseResource() -> void { + pagPlayer.reset(); + surface.reset(); + if (context != nullptr) { + if (frameBuffer != nullptr) { + context->makeCurrent(offscreenSurface.get()); + frameBuffer->release(); + frameBuffer.reset(); + context->doneCurrent(); + } + context.reset(); + } + offscreenSurface.reset(); +} + +auto PAGPlayTask::startInternal() -> void { + float frameRate = pagFile->frameRate(); + Frame totalFrame = TimeToFrame(pagFile->duration(), frameRate); + while (currentFrame < totalFrame) { + if (isNeedRenderCurrentFrame()) { + pagFile->setCurrentTime(FrameToTime(currentFrame, frameRate)); + pagPlayer->flush(); + onFrameFlush(pagPlayer->getProgress()); + } + currentFrame++; + if (isWorking != true) { + releaseResource(); + workerThread.exit(0); + return; + } + } + isWorking = false; + int result = onFinish(); + currentFrame = 0; + releaseResource(); + Q_EMIT taskFinished(result, filePath); + if (openAfterExport) { + Utils::openInFinder(filePath, true); + } + workerThread.exit(0); +} + +auto PAGPlayTask::initOpenGLEnvironment() -> void { + if (surface != nullptr) { + return; + } + pagPlayer = std::make_unique(); + context = std::make_unique(); + context->create(); + offscreenSurface = std::make_unique(); + offscreenSurface->setFormat(context->format()); + offscreenSurface->create(); + context->makeCurrent(offscreenSurface.get()); + frameBuffer = std::make_unique( + QSize(pagFile->width(), pagFile->height()), GL_TEXTURE_2D); + GLFrameBufferInfo frameBufferInfo; + frameBufferInfo.id = frameBuffer->handle(); + BackendRenderTarget renderTarget = + BackendRenderTarget(frameBufferInfo, frameBuffer->width(), frameBuffer->height()); + surface = pag::PAGSurface::MakeFrom(renderTarget, pag::ImageOrigin::TopLeft); + pagPlayer->setSurface(surface); + pagPlayer->setComposition(pagFile); + context->doneCurrent(); + context->moveToThread(&workerThread); + offscreenSurface->moveToThread(&workerThread); +} + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/task/PAGTask.h b/viewer/src/task/PAGTask.h new file mode 100644 index 0000000000..6b5f334c23 --- /dev/null +++ b/viewer/src/task/PAGTask.h @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "pag/pag.h" + +namespace pag { + +class PAGTask : public QObject { + Q_OBJECT + public: + PAGTask(); + + Q_PROPERTY(bool visible READ getVisible NOTIFY visibleChanged) + Q_PROPERTY(double progress READ getProgress NOTIFY progressChanged) + + virtual auto getVisible() const -> bool; + virtual auto getProgress() const -> double; + + Q_SIGNAL void taskFinished(int result, QString filePath); + Q_SIGNAL void visibleChanged(bool visible); + Q_SIGNAL void progressChanged(double progress); + + Q_INVOKABLE virtual void start() = 0; + Q_INVOKABLE virtual void stop() = 0; + + protected: + virtual auto onBegin() -> void = 0; + virtual auto onFinish() -> int = 0; + + private: + virtual auto startInternal() -> void = 0; + + protected: + bool visible = false; + double progress = 0.0; +}; + +class PAGPlayTask : public PAGTask { + Q_OBJECT + public: + explicit PAGPlayTask(std::shared_ptr& pagFile, const QString& filePath); + ~PAGPlayTask() override; + + auto start() -> void override; + auto stop() -> void override; + + protected: + auto onBegin() -> void override; + auto onFinish() -> int override; + virtual auto onFrameFlush(double progress) -> void; + virtual auto isNeedRenderCurrentFrame() -> bool; + auto releaseResource() -> void; + + private: + auto startInternal() -> void override; + auto initOpenGLEnvironment() -> void; + + protected: + bool openAfterExport = true; + Frame currentFrame = 0; + QString filePath = ""; + std::shared_ptr pagFile = nullptr; + std::unique_ptr pagPlayer = nullptr; + std::shared_ptr surface = nullptr; + std::unique_ptr context = nullptr; + std::unique_ptr offscreenSurface = nullptr; + std::unique_ptr frameBuffer = nullptr; + + private: + QThread workerThread; + std::atomic_bool isWorking = false; +}; + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/task/PAGTaskFactory.cpp b/viewer/src/task/PAGTaskFactory.cpp new file mode 100644 index 0000000000..8a7c0650c7 --- /dev/null +++ b/viewer/src/task/PAGTaskFactory.cpp @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PAGTaskFactory.h" +#include +#include +#include +#include "task/export/PAGExportAPNGTask.h" +#include "task/export/PAGExportPNGTask.h" + +namespace pag { + +auto PAGTaskFactory::createTask(PAGTaskType taskType, const QString& outPath, + const QVariantMap& extraParams) -> PAGTask* { + if (pagFile == nullptr) { + return nullptr; + } + + QString path = outPath; + if (path.startsWith("file://")) { + path = QUrl(path).toLocalFile(); + } + + switch (taskType) { + case PAGTaskType_ExportPNG: { + int exportFrame = -1; + if (extraParams.contains("exportFrame")) { + exportFrame = extraParams.value("exportFrame").toInt(); + } + task = new PAGExportPNGTask(pagFile, path, exportFrame); + break; + } + case PAGTaskType_ExportAPNG: { + QFileInfo fileInfo(path); + QString pngFilePath = fileInfo.absolutePath() + "/" + fileInfo.baseName() + "_PNG"; + task = new PAGExportAPNGTask(pagFile, path, pngFilePath); + break; + } + default: { + delete task; + task = nullptr; + break; + } + } + + return task; +} + +auto PAGTaskFactory::resetFile([[maybe_unused]] const std::shared_ptr& pagFile, + const std::string& filePath) -> void { + this->pagFile = PAGFile::Load(filePath); + this->filePath = filePath; +} + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/task/PAGTaskFactory.h b/viewer/src/task/PAGTaskFactory.h new file mode 100644 index 0000000000..c6f5442b21 --- /dev/null +++ b/viewer/src/task/PAGTaskFactory.h @@ -0,0 +1,45 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "PAGTask.h" +#include "pag/pag.h" + +namespace pag { + +class PAGTaskFactory : public QObject { + Q_OBJECT + public: + Q_ENUMS(PAGTaskType) + + enum PAGTaskType { PAGTaskType_None, PAGTaskType_ExportPNG, PAGTaskType_ExportAPNG }; + + Q_INVOKABLE PAGTask* createTask(PAGTaskType taskType, const QString& outPath, + const QVariantMap& extraParams); + + auto resetFile(const std::shared_ptr& pagFile, const std::string& filePath) -> void; + + private: + PAGTask* task = nullptr; + std::string filePath = ""; + std::shared_ptr pagFile = nullptr; +}; + +} // namespace pag diff --git a/viewer/src/task/export/PAGExportAPNGTask.cpp b/viewer/src/task/export/PAGExportAPNGTask.cpp new file mode 100644 index 0000000000..551ed6dbf2 --- /dev/null +++ b/viewer/src/task/export/PAGExportAPNGTask.cpp @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PAGExportAPNGTask.h" +#include +#include "utils/PAGFileUtils.h" +#include "utils/PAGUtils.h" + +namespace pag { + +PAGExportAPNGTask::PAGExportAPNGTask(std::shared_ptr& pagFile, const QString& apngFilePath, + const QString& pngFilePath) + : PAGExportPNGTask(pagFile, pngFilePath), apngFilePath(apngFilePath) { + openAfterExport = false; +} + +auto PAGExportAPNGTask::onFrameFlush(double progress) -> void { + PAGExportPNGTask::onFrameFlush(progress * 0.9); +} + +auto PAGExportAPNGTask::onFinish() -> int { + std::string outPath = apngFilePath.toStdString(); + std::string firstPNGPath = QString("%1/1.png").arg(filePath).toStdString(); + int frameRate = static_cast(pagFile->frameRate()); + Utils::exportAPNGFromPNGSequence(outPath, firstPNGPath, frameRate); + PAGExportPNGTask::onFrameFlush(1.0); + Utils::deleteDir(filePath); + Utils::openInFinder(apngFilePath, true); + return PAGExportPNGTask::onFinish(); +} + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/task/export/PAGExportAPNGTask.h b/viewer/src/task/export/PAGExportAPNGTask.h new file mode 100644 index 0000000000..d4f7287cd4 --- /dev/null +++ b/viewer/src/task/export/PAGExportAPNGTask.h @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "task/export/PAGExportPNGTask.h" + +namespace pag { + +class PAGExportAPNGTask : public PAGExportPNGTask { + Q_OBJECT + public: + explicit PAGExportAPNGTask(std::shared_ptr& pagFile, const QString& apngFilePath, + const QString& pngFilePath); + + protected: + auto onFrameFlush(double progress) -> void override; + auto onFinish() -> int override; + + private: + QString apngFilePath = ""; + int exportFrame = -1; +}; + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/task/export/PAGExportPNGTask.cpp b/viewer/src/task/export/PAGExportPNGTask.cpp new file mode 100644 index 0000000000..d5544820fb --- /dev/null +++ b/viewer/src/task/export/PAGExportPNGTask.cpp @@ -0,0 +1,65 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "task/export/PAGExportPNGTask.h" +#include +#include +#include "utils/PAGFileUtils.h" + +namespace pag { + +const std::string ExportPNGFileSuffix = "png"; + +PAGExportPNGTask::PAGExportPNGTask(std::shared_ptr& pagFile, const QString& filePath, + int exportFrame) + : PAGPlayTask(pagFile, filePath), exportFrame(exportFrame) { + QString lowerFilePath = filePath.toLower(); + if (!lowerFilePath.endsWith(QString(".%1").arg(ExportPNGFileSuffix.c_str()))) { + Utils::makeDir(filePath); + } +} + +auto PAGExportPNGTask::onFrameFlush(double progress) -> void { + PAGPlayTask::onFrameFlush(progress); + QString outPath; + if (exportFrame >= 0) { + outPath = filePath; + } else { + outPath = QString("%1/%2.png").arg(filePath).arg(currentFrame); + } + exportCurrentFrameAsPNG(outPath); +} + +auto PAGExportPNGTask::isNeedRenderCurrentFrame() -> bool { + if (exportFrame < 0) { + return true; + } + return exportFrame == currentFrame; +} + +auto PAGExportPNGTask::exportCurrentFrameAsPNG(const QString& outPath) -> void { + context->makeCurrent(offscreenSurface.get()); + auto image = frameBuffer->toImage(false); + bool result = image.save(outPath, ExportPNGFileSuffix.c_str()); + if (!result) { + qDebug() << "Failed to save image to path: " << outPath; + } + context->doneCurrent(); +} + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/task/export/PAGExportPNGTask.h b/viewer/src/task/export/PAGExportPNGTask.h new file mode 100644 index 0000000000..096677cf8e --- /dev/null +++ b/viewer/src/task/export/PAGExportPNGTask.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "task/PAGTask.h" + +namespace pag { + +class PAGExportPNGTask : public PAGPlayTask { + Q_OBJECT + public: + explicit PAGExportPNGTask(std::shared_ptr& pagFile, const QString& filePath, + int exportFrame = -1); + + protected: + auto onFrameFlush(double progress) -> void override; + auto isNeedRenderCurrentFrame() -> bool override; + + private: + auto exportCurrentFrameAsPNG(const QString& outPath) -> void; + + private: + int exportFrame = -1; +}; + +} // namespace pag \ No newline at end of file diff --git a/viewer/src/utils/PAGFileUtils.cpp b/viewer/src/utils/PAGFileUtils.cpp new file mode 100644 index 0000000000..2ae7061d80 --- /dev/null +++ b/viewer/src/utils/PAGFileUtils.cpp @@ -0,0 +1,86 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "utils/PAGFileUtils.h" +#include +#include +#include +#include "PAGUtilsImpl.h" + +namespace pag::Utils { + +auto openInFinder(const QString& path, bool select) -> void { + QFileInfo fileInfo(path); + if (!fileInfo.exists()) { + return; + } + + if (select) { + openFileInFinder(fileInfo); + } else { + QDesktopServices::openUrl(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); + } +} + +auto deleteFile(const QString& path) -> bool { + QFile file(path); + if (!file.exists()) { + return true; + } + return file.remove(); +} + +auto deleteDir(const QString& path) -> bool { + QDir dir(path); + if (!dir.exists()) { + return true; + } + + dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden); + QFileInfoList fileList = dir.entryInfoList(); + for (auto& fileInfo : fileList) { + if (fileInfo.isFile()) { + if (!fileInfo.dir().remove(fileInfo.fileName())) { + return false; + } + } else { + if (!deleteDir(fileInfo.absoluteFilePath())) { + return false; + } + } + } + + return dir.rmdir(path); +} + +auto makeDir(const QString& path, bool isDir) -> bool { + QString dirPath; + QFileInfo fileInfo(path); + if (isDir) { + dirPath = path; + } else { + dirPath = fileInfo.absolutePath(); + } + QDir dir(dirPath); + if (dir.exists()) { + return true; + } + return dir.mkpath(dirPath); +} + +} // namespace pag::Utils diff --git a/viewer/src/utils/PAGFileUtils.h b/viewer/src/utils/PAGFileUtils.h new file mode 100644 index 0000000000..8de55a27a2 --- /dev/null +++ b/viewer/src/utils/PAGFileUtils.h @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace pag::Utils { + +auto openInFinder(const QString& path, bool select = true) -> void; + +auto deleteFile(const QString& path) -> bool; + +auto deleteDir(const QString& path) -> bool; + +auto makeDir(const QString& path, bool isDir = true) -> bool; + +} // namespace pag::Utils diff --git a/viewer/src/utils/PAGUtils.cpp b/viewer/src/utils/PAGUtils.cpp new file mode 100644 index 0000000000..45d09457f0 --- /dev/null +++ b/viewer/src/utils/PAGUtils.cpp @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PAGUtils.h" +#include "utils/apng/APNGAssembler.h" + +namespace pag::Utils { + +auto exportAPNGFromPNGSequence(const std::string& outPath, const std::string& firstPNGPath, + int frameRate) -> int { + auto apngAssembler = APNGAssembler(); + + return apngAssembler.exportFromPNGSequence(outPath, firstPNGPath, frameRate); +} + +} // namespace pag::Utils \ No newline at end of file diff --git a/viewer/src/utils/PAGUtils.h b/viewer/src/utils/PAGUtils.h new file mode 100644 index 0000000000..83a27c857f --- /dev/null +++ b/viewer/src/utils/PAGUtils.h @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace pag::Utils { + +auto exportAPNGFromPNGSequence(const std::string& outPath, const std::string& firstPNGPath, + int frameRate) -> int; + +} // namespace pag::Utils \ No newline at end of file diff --git a/viewer/src/utils/apng/APNGAssembler.cpp b/viewer/src/utils/apng/APNGAssembler.cpp new file mode 100644 index 0000000000..ef82940196 --- /dev/null +++ b/viewer/src/utils/apng/APNGAssembler.cpp @@ -0,0 +1,776 @@ +/* APNG Assembler 2.91 + * + * This program creates APNG animation from PNG/TGA image sequence. + * + * http://apngasm.sourceforge.net/ + * + * Copyright (c) 2009-2016 Max Stepin + * maxst at users.sourceforge.net + * + * zlib license + * ------------ + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + +#include "APNGAssembler.h" +#include "png.h" + +APNGAssembler::APNGAssembler() = default; + +APNGAssembler::~APNGAssembler() = default; + +auto APNGAssembler::exportFromPNGSequence(const std::string& outPath, + const std::string& firstPNGPath, int frameRate) -> int { + std::string str1 = outPath; + std::string str2 = firstPNGPath; + char* szOut = str1.data(); + char* szImage = str2.data(); + + delay_den = frameRate; + + int ret = load_image_sequence(szImage, first, img, delay_num, delay_den, &coltype); + if (ret) { + return ret; + } + + for (auto& image : img) { + if (image.type != coltype) { + optim_upconvert(&image, coltype); + } + } + + if (coltype == 6 || coltype == 4) { + for (auto& image : img) { + optim_dirty_transp(&image); + } + } + + optim_duplicates(img, first); + + if (!keep_coltype) { + optim_downconvert(img); + } + + coltype = static_cast(img[0].type); + if (coltype == 3 && !keep_palette) { + optim_palette(img); + } + if (coltype == 2 || coltype == 0) { + optim_add_transp(img); + } + + ret = save_apng(szOut, img, loops, first, deflate_method, iter); + for (auto& image : img) { + image.free(); + } + if (ret) { + return ret; + } + + return 0; +}; + +auto APNGAssembler::write_chunk(FILE* f, const char* name, unsigned char* data, unsigned int length) + -> void { + unsigned char buf[4]; + unsigned int crc = (unsigned int)crc32(0, Z_NULL, 0); + + png_save_uint_32(buf, length); + fwrite(buf, 1, 4, f); + fwrite(name, 1, 4, f); + crc = (unsigned int)crc32(crc, (const Bytef*)name, 4); + + if (memcmp(name, "fdAT", 4) == 0) { + png_save_uint_32(buf, next_seq_num++); + fwrite(buf, 1, 4, f); + crc = (unsigned int)crc32(crc, buf, 4); + length -= 4; + } + + if (data != NULL && length > 0) { + fwrite(data, 1, length, f); + crc = (unsigned int)crc32(crc, data, length); + } + + png_save_uint_32(buf, crc); + fwrite(buf, 1, 4, f); +} + +auto APNGAssembler::write_IDATs(FILE* f, unsigned int frame, unsigned char* data, + unsigned int length, unsigned int idat_size) -> void { + unsigned int z_cmf = data[0]; + if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) { + if (length >= 2) { + unsigned int z_cinfo = z_cmf >> 4; + unsigned int half_z_window_size = 1 << (z_cinfo + 7); + while (idat_size <= half_z_window_size && half_z_window_size >= 256) { + z_cinfo--; + half_z_window_size >>= 1; + } + z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); + if (data[0] != (unsigned char)z_cmf) { + data[0] = (unsigned char)z_cmf; + data[1] &= 0xe0; + data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f); + } + } + } + + while (length > 0) { + unsigned int ds = length; + if (ds > 32768) ds = 32768; + + if (frame == 0) write_chunk(f, "IDAT", data, ds); + else + write_chunk(f, "fdAT", data, ds + 4); + + data += ds; + length -= ds; + } +} + +auto APNGAssembler::deflate_rect_op(Image* image, int x, int y, int w, int h, int bpp, + int zbuf_size, int n) -> void { + op_zstream1.data_type = Z_BINARY; + op_zstream1.next_out = op_zbuf1; + op_zstream1.avail_out = zbuf_size; + + op_zstream2.data_type = Z_BINARY; + op_zstream2.next_out = op_zbuf2; + op_zstream2.avail_out = zbuf_size; + + process_rect(image, x * bpp, w * bpp, y, h, bpp, NULL); + + deflate(&op_zstream1, Z_FINISH); + deflate(&op_zstream2, Z_FINISH); + op[n].image = image; + + if (op_zstream1.total_out < op_zstream2.total_out) { + op[n].size = (unsigned int)op_zstream1.total_out; + op[n].filters = 0; + } else { + op[n].size = (unsigned int)op_zstream2.total_out; + op[n].filters = 1; + } + op[n].x = x; + op[n].y = y; + op[n].w = w; + op[n].h = h; + op[n].valid = 1; + deflateReset(&op_zstream1); + deflateReset(&op_zstream2); +} + +auto APNGAssembler::deflate_rect_fin([[maybe_unused]] int deflate_method, [[maybe_unused]] int iter, + unsigned char* zbuf, unsigned int* zsize, int bpp, + unsigned char* dest, int zbuf_size, int n) -> void { + Image* image = op[n].image; + int xbytes = op[n].x * bpp; + int rowbytes = op[n].w * bpp; + int y = op[n].y; + int h = op[n].h; + + if (op[n].filters == 0) { + unsigned char* dp = dest; + for (int j = y; j < y + h; j++) { + *dp++ = 0; + memcpy(dp, image->rows[j] + xbytes, rowbytes); + dp += rowbytes; + } + } else + process_rect(image, xbytes, rowbytes, y, h, bpp, dest); + + z_stream fin_zstream; + + fin_zstream.data_type = Z_BINARY; + fin_zstream.zalloc = Z_NULL; + fin_zstream.zfree = Z_NULL; + fin_zstream.opaque = Z_NULL; + deflateInit2(&fin_zstream, Z_BEST_COMPRESSION, 8, 15, 8, + op[n].filters ? Z_FILTERED : Z_DEFAULT_STRATEGY); + + fin_zstream.next_out = zbuf; + fin_zstream.avail_out = zbuf_size; + fin_zstream.next_in = dest; + fin_zstream.avail_in = h * (rowbytes + 1); + deflate(&fin_zstream, Z_FINISH); + *zsize = (unsigned int)fin_zstream.total_out; + deflateEnd(&fin_zstream); +} + +auto APNGAssembler::get_rect(unsigned int w, unsigned int h, Image* image1, Image* image2, + Image* temp, unsigned int bpp, int zbuf_size, unsigned int has_tcolor, + unsigned int tcolor, int n) -> void { + unsigned int i, j, x0, y0, w0, h0; + unsigned int x_min = w - 1; + unsigned int y_min = h - 1; + unsigned int x_max = 0; + unsigned int y_max = 0; + unsigned int diffnum = 0; + unsigned int over_is_possible = 1; + + if (!has_tcolor) over_is_possible = 0; + + if (bpp == 1) { + for (j = 0; j < h; j++) { + unsigned char* pa = image1->rows[j]; + unsigned char* pb = image2->rows[j]; + unsigned char* pc = temp->rows[j]; + for (i = 0; i < w; i++) { + unsigned char c = *pb++; + if (*pa++ != c) { + diffnum++; + if (has_tcolor && c == tcolor) over_is_possible = 0; + if (i < x_min) x_min = i; + if (i > x_max) x_max = i; + if (j < y_min) y_min = j; + if (j > y_max) y_max = j; + } else + c = tcolor; + + *pc++ = c; + } + } + } else if (bpp == 2) { + for (j = 0; j < h; j++) { + unsigned short* pa = (unsigned short*)image1->rows[j]; + unsigned short* pb = (unsigned short*)image2->rows[j]; + unsigned short* pc = (unsigned short*)temp->rows[j]; + for (i = 0; i < w; i++) { + unsigned int c1 = *pa++; + unsigned int c2 = *pb++; + if ((c1 != c2) && ((c1 >> 8) || (c2 >> 8))) { + diffnum++; + if ((c2 >> 8) != 0xFF) over_is_possible = 0; + if (i < x_min) x_min = i; + if (i > x_max) x_max = i; + if (j < y_min) y_min = j; + if (j > y_max) y_max = j; + } else + c2 = 0; + + *pc++ = c2; + } + } + } else if (bpp == 3) { + for (j = 0; j < h; j++) { + unsigned char* pa = image1->rows[j]; + unsigned char* pb = image2->rows[j]; + unsigned char* pc = temp->rows[j]; + for (i = 0; i < w; i++) { + unsigned int c1 = (pa[2] << 16) + (pa[1] << 8) + pa[0]; + unsigned int c2 = (pb[2] << 16) + (pb[1] << 8) + pb[0]; + if (c1 != c2) { + diffnum++; + if (has_tcolor && c2 == tcolor) over_is_possible = 0; + if (i < x_min) x_min = i; + if (i > x_max) x_max = i; + if (j < y_min) y_min = j; + if (j > y_max) y_max = j; + } else + c2 = tcolor; + + memcpy(pc, &c2, 3); + pa += 3; + pb += 3; + pc += 3; + } + } + } else if (bpp == 4) { + for (j = 0; j < h; j++) { + unsigned int* pa = (unsigned int*)image1->rows[j]; + unsigned int* pb = (unsigned int*)image2->rows[j]; + unsigned int* pc = (unsigned int*)temp->rows[j]; + for (i = 0; i < w; i++) { + unsigned int c1 = *pa++; + unsigned int c2 = *pb++; + if ((c1 != c2) && ((c1 >> 24) || (c2 >> 24))) { + diffnum++; + if ((c2 >> 24) != 0xFF) over_is_possible = 0; + if (i < x_min) x_min = i; + if (i > x_max) x_max = i; + if (j < y_min) y_min = j; + if (j > y_max) y_max = j; + } else + c2 = 0; + + *pc++ = c2; + } + } + } + + if (diffnum == 0) { + x0 = y0 = 0; + w0 = h0 = 1; + } else { + x0 = x_min; + y0 = y_min; + w0 = x_max - x_min + 1; + h0 = y_max - y_min + 1; + } + + deflate_rect_op(image2, x0, y0, w0, h0, bpp, zbuf_size, n * 2); + + if (over_is_possible) deflate_rect_op(temp, x0, y0, w0, h0, bpp, zbuf_size, n * 2 + 1); +} + +auto APNGAssembler::process_rect(Image* image, int xbytes, int rowbytes, int y, int h, int bpp, + unsigned char* dest) -> void { + int i, j, v; + int a, b, c, pa, pb, pc, p; + unsigned char* prev = NULL; + unsigned char* dp = dest; + unsigned char* out; + + for (j = y; j < y + h; j++) { + unsigned char* row = image->rows[j] + xbytes; + unsigned int sum = 0; + unsigned char* best_row = row_buf; + unsigned int mins = ((unsigned int)(-1)) >> 1; + + out = row_buf + 1; + for (i = 0; i < rowbytes; i++) { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + mins = sum; + + sum = 0; + out = sub_row + 1; + for (i = 0; i < bpp; i++) { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) { + v = out[i] = row[i] - row[i - bpp]; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) break; + } + if (sum < mins) { + mins = sum; + best_row = sub_row; + } + + if (prev) { + sum = 0; + out = up_row + 1; + for (i = 0; i < rowbytes; i++) { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) break; + } + if (sum < mins) { + mins = sum; + best_row = up_row; + } + + sum = 0; + out = avg_row + 1; + for (i = 0; i < bpp; i++) { + v = out[i] = row[i] - prev[i] / 2; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) { + v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) break; + } + if (sum < mins) { + mins = sum; + best_row = avg_row; + } + + sum = 0; + out = paeth_row + 1; + for (i = 0; i < bpp; i++) { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) { + a = row[i - bpp]; + b = prev[i]; + c = prev[i - bpp]; + p = b - c; + pc = a - c; + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + v = out[i] = row[i] - p; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) break; + } + if (sum < mins) { + best_row = paeth_row; + } + } + + if (dest == NULL) { + // deflate_rect_op() + op_zstream1.next_in = row_buf; + op_zstream1.avail_in = rowbytes + 1; + deflate(&op_zstream1, Z_NO_FLUSH); + + op_zstream2.next_in = best_row; + op_zstream2.avail_in = rowbytes + 1; + deflate(&op_zstream2, Z_NO_FLUSH); + } else { + // deflate_rect_fin() + memcpy(dp, best_row, rowbytes + 1); + dp += rowbytes + 1; + } + + prev = row; + } +} + +auto APNGAssembler::load_image_sequence(char* szImage, [[maybe_unused]] unsigned int first, + std::vector& img, int delay_num, int delay_den, + unsigned char* coltype) -> int { + char szFormat[256]; + char szNext[256]; + char* szExt = strrchr(szImage, '.'); + unsigned int i, cur = 0, frames = 0; + FILE* f; + + if (szExt == NULL || szExt == szImage) { + printf("Error: *%s sequence not found\n", szExt); + return 1; + } else if (*(szExt - 1) == '*') { + f = NULL; + for (i = 1; i < 6; i++) { + strcpy(szFormat, szImage); + snprintf(szFormat + (szExt - 1 - szImage), sizeof(szFormat) - (szExt - 1 - szImage), + "%%0%dd%%s", i); + cur = 0; + snprintf(szNext, sizeof(szNext), szFormat, cur, szExt); + if ((f = fopen(szNext, "rb")) != 0) break; + cur = 1; + snprintf(szNext, sizeof(szNext), szFormat, cur, szExt); + if ((f = fopen(szNext, "rb")) != 0) break; + } + + if (f != NULL) { + fclose(f); + } else { + printf("Error: *%s sequence not found\n", szExt); + return 1; + } + } else { + for (i = 0; i < 6; i++) { + if (szImage == szExt - i) break; + if (*(szExt - i - 1) < '0') break; + if (*(szExt - i - 1) > '9') break; + } + cur = static_cast(atoi(szExt - i)); + strcpy(szFormat, szImage); + snprintf(szFormat + (szExt - i - szImage), sizeof(szFormat) - (szExt - i - szImage), + "%%0%dd%%s", i); + strcpy(szNext, szImage); + } + + if ((f = fopen(szNext, "rb")) == 0) { + printf("Error: can't open the file '%s'", szNext); + return 1; + } + + do { + frames++; + fclose(f); + snprintf(szNext, sizeof(szNext), szFormat, cur + frames, szExt); + f = fopen(szNext, "rb"); + } while (f != 0); + + img.resize(frames); + + for (i = 0; i < frames; i++) { + snprintf(szNext, sizeof(szNext), szFormat, cur + i, szExt); + + if (load_image(szNext, &img[i])) return 1; + + img[i].delay_num = static_cast(delay_num); + img[i].delay_den = static_cast(delay_den); + + snprintf(szNext, sizeof(szNext), szFormat, cur + i, ".txt"); + f = fopen(szNext, "rt"); + if (f != 0) { + char szStr[256]; + if (fgets(szStr, 256, f) != NULL) { + int d1, d2; + if (sscanf(szStr, "delay=%d/%d", &d1, &d2) == 2) { + if (d1 != 0) img[i].delay_num = static_cast(d1); + if (d2 != 0) img[i].delay_den = static_cast(d2); + } + } + fclose(f); + } + + if (img[0].w != img[i].w || img[0].h != img[i].h) { + printf("Error at %s: different image size\n", szNext); + return 1; + } + } + + *coltype = find_common_coltype(img); + return 0; +} + +auto APNGAssembler::save_apng(const char* szOut, std::vector& img, unsigned int loops, + unsigned int first, int deflate_method, [[maybe_unused]] int iter) + -> int { + unsigned char coltype = img[0].type; + unsigned int has_tcolor = 0; + unsigned int tcolor = 0; + + if (coltype == 0) { + if (img[0].ts) { + has_tcolor = 1; + tcolor = img[0].tr[1]; + } + } else if (coltype == 2) { + if (img[0].ts) { + has_tcolor = 1; + tcolor = (((img[0].tr[5] << 8) + img[0].tr[3]) << 8) + img[0].tr[1]; + } + } else if (coltype == 3) { + for (int c = 0; c < img[0].ts; c++) + if (img[0].tr[c] == 0) { + has_tcolor = 1; + tcolor = c; + break; + } + } else + has_tcolor = 1; + + FILE* f; + if ((f = fopen(szOut, "wb")) == 0) { + printf("Error: can't save to file '%s'\n", szOut); + return 1; + } + + unsigned char buf_IHDR[13]; + unsigned char buf_acTL[8]; + unsigned char buf_fcTL[26]; + + unsigned int width = img[0].w; + unsigned int height = img[0].h; + unsigned int bpp = img[0].bpp; + unsigned int rowbytes = width * bpp; + unsigned int visible = (unsigned int)(img.size() - first); + + Image temp, over1, over2, over3, rest; + temp.init(&img[0]); + over1.init(&img[0]); + over2.init(&img[0]); + over3.init(&img[0]); + rest.init(&img[0]); + unsigned char* dest = new unsigned char[(rowbytes + 1) * height]; + + png_save_uint_32(buf_IHDR, width); + png_save_uint_32(buf_IHDR + 4, height); + buf_IHDR[8] = 8; + buf_IHDR[9] = coltype; + buf_IHDR[10] = 0; + buf_IHDR[11] = 0; + buf_IHDR[12] = 0; + + png_save_uint_32(buf_acTL, visible); + png_save_uint_32(buf_acTL + 4, loops); + + fwrite(png_sign, 1, 8, f); + + write_chunk(f, "IHDR", buf_IHDR, 13); + + if (img.size() > 1) write_chunk(f, "acTL", buf_acTL, 8); + else + first = 0; + + if (img[0].ps > 0) write_chunk(f, "PLTE", (unsigned char*)(&img[0].pl), img[0].ps * 3); + + if (img[0].ts > 0) write_chunk(f, "tRNS", img[0].tr, img[0].ts); + + op_zstream1.data_type = Z_BINARY; + op_zstream1.zalloc = Z_NULL; + op_zstream1.zfree = Z_NULL; + op_zstream1.opaque = Z_NULL; + deflateInit2(&op_zstream1, Z_BEST_SPEED + 1, 8, 15, 8, Z_DEFAULT_STRATEGY); + + op_zstream2.data_type = Z_BINARY; + op_zstream2.zalloc = Z_NULL; + op_zstream2.zfree = Z_NULL; + op_zstream2.opaque = Z_NULL; + deflateInit2(&op_zstream2, Z_BEST_SPEED + 1, 8, 15, 8, Z_FILTERED); + + unsigned int idat_size = (rowbytes + 1) * height; + unsigned int zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11; + + unsigned char* zbuf = new unsigned char[zbuf_size]; + op_zbuf1 = new unsigned char[zbuf_size]; + op_zbuf2 = new unsigned char[zbuf_size]; + row_buf = new unsigned char[rowbytes + 1]; + sub_row = new unsigned char[rowbytes + 1]; + up_row = new unsigned char[rowbytes + 1]; + avg_row = new unsigned char[rowbytes + 1]; + paeth_row = new unsigned char[rowbytes + 1]; + + row_buf[0] = 0; + sub_row[0] = 1; + up_row[0] = 2; + avg_row[0] = 3; + paeth_row[0] = 4; + + unsigned int i, j, k; + unsigned int zsize = 0; + unsigned int x0 = 0; + unsigned int y0 = 0; + unsigned int w0 = width; + unsigned int h0 = height; + unsigned char bop = 0; + unsigned char dop = 0; + next_seq_num = 0; + + for (j = 0; j < 6; j++) op[j].valid = 0; + deflate_rect_op(&img[0], x0, y0, w0, h0, bpp, zbuf_size, 0); + deflate_rect_fin(deflate_method, iter, zbuf, &zsize, bpp, dest, zbuf_size, 0); + + if (first) { + write_IDATs(f, 0, zbuf, zsize, idat_size); + + printf("saving %s (frame %d of %d)\n", szOut, 1, visible); + for (j = 0; j < 6; j++) op[j].valid = 0; + deflate_rect_op(&img[1], x0, y0, w0, h0, bpp, zbuf_size, 0); + deflate_rect_fin(deflate_method, iter, zbuf, &zsize, bpp, dest, zbuf_size, 0); + } + + for (i = first; i < img.size() - 1; i++) { + unsigned int op_min; + int op_best; + +#ifdef DEBUG +// printf("saving %s (frame %d of %d)\n", szOut, i-first+2, visible); +#endif + + for (j = 0; j < 6; j++) op[j].valid = 0; + + /* dispose = none */ + get_rect(width, height, &img[i], &img[i + 1], &over1, bpp, zbuf_size, has_tcolor, tcolor, 0); + + /* dispose = background */ + if (has_tcolor) { + for (j = 0; j < height; j++) memcpy(temp.rows[j], img[i].rows[j], rowbytes); + if (coltype == 2) + for (j = 0; j < h0; j++) + for (k = 0; k < w0; k++) memcpy(temp.rows[j + y0] + (k + x0) * 3, &tcolor, 3); + else + for (j = 0; j < h0; j++) memset(temp.rows[j + y0] + x0 * bpp, tcolor, w0 * bpp); + + get_rect(width, height, &temp, &img[i + 1], &over2, bpp, zbuf_size, has_tcolor, tcolor, 1); + } + + /* dispose = previous */ + if (i > first) + get_rect(width, height, &rest, &img[i + 1], &over3, bpp, zbuf_size, has_tcolor, tcolor, 2); + + op_min = op[0].size; + op_best = 0; + for (j = 1; j < 6; j++) + if (op[j].valid) { + if (op[j].size < op_min) { + op_min = op[j].size; + op_best = j; + } + } + + dop = op_best >> 1; + + png_save_uint_32(buf_fcTL, next_seq_num++); + png_save_uint_32(buf_fcTL + 4, w0); + png_save_uint_32(buf_fcTL + 8, h0); + png_save_uint_32(buf_fcTL + 12, x0); + png_save_uint_32(buf_fcTL + 16, y0); + png_save_uint_16(buf_fcTL + 20, img[i].delay_num); + png_save_uint_16(buf_fcTL + 22, img[i].delay_den); + buf_fcTL[24] = dop; + buf_fcTL[25] = bop; + write_chunk(f, "fcTL", buf_fcTL, 26); + + write_IDATs(f, i, zbuf, zsize, idat_size); + + /* process apng dispose - exportStart */ + if (dop != 2) + for (j = 0; j < height; j++) memcpy(rest.rows[j], img[i].rows[j], rowbytes); + + if (dop == 1) { + if (coltype == 2) + for (j = 0; j < h0; j++) + for (k = 0; k < w0; k++) memcpy(rest.rows[j + y0] + (k + x0) * 3, &tcolor, 3); + else + for (j = 0; j < h0; j++) memset(rest.rows[j + y0] + x0 * bpp, tcolor, w0 * bpp); + } + /* process apng dispose - end */ + + x0 = op[op_best].x; + y0 = op[op_best].y; + w0 = op[op_best].w; + h0 = op[op_best].h; + bop = op_best & 1; + + deflate_rect_fin(deflate_method, iter, zbuf, &zsize, bpp, dest, zbuf_size, op_best); + } + + if (img.size() > 1) { + png_save_uint_32(buf_fcTL, next_seq_num++); + png_save_uint_32(buf_fcTL + 4, w0); + png_save_uint_32(buf_fcTL + 8, h0); + png_save_uint_32(buf_fcTL + 12, x0); + png_save_uint_32(buf_fcTL + 16, y0); + png_save_uint_16(buf_fcTL + 20, img.back().delay_num); + png_save_uint_16(buf_fcTL + 22, img.back().delay_den); + buf_fcTL[24] = 0; + buf_fcTL[25] = bop; + write_chunk(f, "fcTL", buf_fcTL, 26); + } + + write_IDATs(f, (unsigned int)(img.size() - 1), zbuf, zsize, idat_size); + + write_chunk(f, "tEXt", png_Software, 28); + write_chunk(f, "IEND", 0, 0); + fclose(f); + + delete[] zbuf; + delete[] op_zbuf1; + delete[] op_zbuf2; + delete[] row_buf; + delete[] sub_row; + delete[] up_row; + delete[] avg_row; + delete[] paeth_row; + + deflateEnd(&op_zstream1); + deflateEnd(&op_zstream2); + + temp.free(); + over1.free(); + over2.free(); + over3.free(); + rest.free(); + delete[] dest; + + return 0; +} \ No newline at end of file diff --git a/viewer/src/utils/apng/APNGAssembler.h b/viewer/src/utils/apng/APNGAssembler.h new file mode 100644 index 0000000000..657a8cfe7d --- /dev/null +++ b/viewer/src/utils/apng/APNGAssembler.h @@ -0,0 +1,93 @@ +/* APNG Assembler 2.91 + * + * This program creates APNG animation from PNG/TGA image sequence. + * + * http://apngasm.sourceforge.net/ + * + * Copyright (c) 2009-2016 Max Stepin + * maxst at users.sourceforge.net + * + * zlib license + * ------------ + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + +#pragma once + +#include +#include "image.h" +#include "zlib.h" + +class APNGAssembler { + public: + typedef struct { + Image* image; + unsigned int size; + int x, y, w, h, valid, filters; + } OP; + APNGAssembler(); + ~APNGAssembler(); + + int exportFromPNGSequence(const std::string& outPath, const std::string& firstPNGPath, + int frameRate); + + private: + void write_chunk(FILE* f, const char* name, unsigned char* data, unsigned int length); + void write_IDATs(FILE* f, unsigned int frame, unsigned char* data, unsigned int length, + unsigned int idat_size); + void deflate_rect_op(Image* image, int x, int y, int w, int h, int bpp, int zbuf_size, int n); + void deflate_rect_fin(int deflate_method, int iter, unsigned char* zbuf, unsigned int* zsize, + int bpp, unsigned char* dest, int zbuf_size, int n); + void get_rect(unsigned int w, unsigned int h, Image* image1, Image* image2, Image* temp, + unsigned int bpp, int zbuf_size, unsigned int has_tcolor, unsigned int tcolor, + int n); + void process_rect(Image* image, int xbytes, int rowbytes, int y, int h, int bpp, + unsigned char* dest); + int load_image_sequence(char* szImage, unsigned int first, std::vector& img, int delay_num, + int delay_den, unsigned char* coltype); + int save_apng(const char* szOut, std::vector& img, unsigned int loops, unsigned int first, + int deflate_method, int iter); + + private: + unsigned char* op_zbuf1 = nullptr; + unsigned char* op_zbuf2 = nullptr; + unsigned char* row_buf = nullptr; + unsigned char* sub_row = nullptr; + unsigned char* up_row = nullptr; + unsigned char* avg_row = nullptr; + unsigned char* paeth_row = nullptr; + unsigned char coltype = 6; + unsigned char png_sign[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + unsigned char png_Software[28] = {83, 111, 102, 116, 119, 97, 114, 101, '\0', 65, 80, 78, 71, 32, + 65, 115, 115, 101, 109, 98, 108, 101, 114, 32, 50, 46, 57, 49}; + + unsigned int first = 0; + unsigned int loops = 0; + unsigned int next_seq_num = 0; + int iter = 15; + int delay_num = 1; + int delay_den = -1; + int keep_palette = 0; + int keep_coltype = 1; + int deflate_method = 0; + z_stream op_zstream1{}; + z_stream op_zstream2{}; + OP op[6]{}; + std::vector img; +}; diff --git a/viewer/src/utils/apng/image.cpp b/viewer/src/utils/apng/image.cpp new file mode 100644 index 0000000000..4395794bd6 --- /dev/null +++ b/viewer/src/utils/apng/image.cpp @@ -0,0 +1,1116 @@ +/* Image library + * + * Copyright (c) 2016 Max Stepin + * maxst at users.sourceforge.net + * + * zlib license + * ------------ + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + +#include "image.h" +#include +#include +#include "png.h" +#include "zlib.h" + +int load_png(char* szName, Image* image) { + FILE* f; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info_ptr = png_create_info_struct(png_ptr); + + if (!png_ptr || !info_ptr) return 1; + + if ((f = fopen(szName, "rb")) == 0) { + printf("Error: can't open '%s'\n", szName); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + printf("Error: can't load '%s'\n", szName); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(f); + return 1; + } + + png_init_io(png_ptr, f); + png_read_info(png_ptr, info_ptr); + unsigned int depth = png_get_bit_depth(png_ptr, info_ptr); + if (depth < 8) { + if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) png_set_packing(png_ptr); + else + png_set_expand(png_ptr); + } else if (depth > 8) { + png_set_expand(png_ptr); + png_set_strip_16(png_ptr); + } + (void)png_set_interlace_handling(png_ptr); + png_read_update_info(png_ptr, info_ptr); + unsigned int w = png_get_image_width(png_ptr, info_ptr); + unsigned int h = png_get_image_height(png_ptr, info_ptr); + unsigned int bpp = png_get_channels(png_ptr, info_ptr); + unsigned int type = png_get_color_type(png_ptr, info_ptr); + image->init(w, h, bpp, type); + + png_colorp palette; + png_color_16p trans_color; + png_bytep trans_alpha; + + if (png_get_PLTE(png_ptr, info_ptr, &palette, &image->ps)) + memcpy(image->pl, palette, image->ps * 3); + + if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &image->ts, &trans_color) && image->ts > 0) { + if (type == PNG_COLOR_TYPE_GRAY) { + image->tr[0] = 0; + image->tr[1] = trans_color->gray & 0xFF; + image->ts = 2; + } else if (type == PNG_COLOR_TYPE_RGB) { + image->tr[0] = 0; + image->tr[1] = trans_color->red & 0xFF; + image->tr[2] = 0; + image->tr[3] = trans_color->green & 0xFF; + image->tr[4] = 0; + image->tr[5] = trans_color->blue & 0xFF; + image->ts = 6; + } else if (type == PNG_COLOR_TYPE_PALETTE) + memcpy(image->tr, trans_alpha, image->ts); + } + + png_read_image(png_ptr, image->rows); + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(f); + return 0; +} + +int load_tga(char* szName, Image* image) { + FILE* f; + unsigned int i, j, k, n; + unsigned int w, h; + unsigned int compressed, top_bottom; + unsigned char c; + unsigned char col[4]; + unsigned char header[18]; + + if ((f = fopen(szName, "rb")) == 0) { + printf("Error: can't open '%s'\n", szName); + return 1; + } + + if (fread(&header, 1, 18, f) != 18) goto fail; + + w = header[12] + header[13] * 256; + h = header[14] + header[15] * 256; + compressed = header[2] & 8; + top_bottom = header[17] & 0x20; + + if ((header[2] & 7) == 1 && header[16] == 8 && header[1] == 1 && header[7] == 24) + image->init(w, h, 1, 3); + else if ((header[2] & 7) == 3 && header[16] == 8) + image->init(w, h, 1, 0); + else if ((header[2] & 7) == 2 && header[16] == 24) + image->init(w, h, 3, 2); + else if ((header[2] & 7) == 2 && header[16] == 32) + image->init(w, h, 4, 6); + else + goto fail; + + if (header[0] != 0) fseek(f, header[0], SEEK_CUR); + + if (header[1] == 1) { + unsigned int start = header[3] + header[4] * 256; + unsigned int size = header[5] + header[6] * 256; + for (i = start; i < start + size && i < 256; i++) { + if (fread(&col, 1, 3, f) != 3) goto fail; + image->pl[i].r = col[2]; + image->pl[i].g = col[1]; + image->pl[i].b = col[0]; + } + image->ps = i; + if (start + size > 256) fseek(f, (start + size - 256) * 3, SEEK_CUR); + } + + for (j = 0; j < h; j++) { + unsigned char* row = image->rows[(top_bottom) ? j : h - 1 - j]; + if (compressed == 0) { + if (image->bpp >= 3) { + for (i = 0; i < w; i++) { + if (fread(&col, 1, image->bpp, f) != image->bpp) goto fail; + *row++ = col[2]; + *row++ = col[1]; + *row++ = col[0]; + if (image->bpp == 4) *row++ = col[3]; + } + } else { + if (fread(row, 1, w, f) != w) goto fail; + } + } else { + i = 0; + while (i < w) { + if (fread(&c, 1, 1, f) != 1) goto fail; + n = (c & 0x7F) + 1; + + if ((c & 0x80) != 0) { + if (image->bpp >= 3) { + if (fread(&col, 1, image->bpp, f) != image->bpp) goto fail; + for (k = 0; k < n; k++) { + *row++ = col[2]; + *row++ = col[1]; + *row++ = col[0]; + if (image->bpp == 4) *row++ = col[3]; + } + } else { + if (fread(&col, 1, 1, f) != 1) goto fail; + memset(row, col[0], n); + row += n; + } + } else { + if (image->bpp >= 3) { + for (k = 0; k < n; k++) { + if (fread(&col, 1, image->bpp, f) != image->bpp) goto fail; + *row++ = col[2]; + *row++ = col[1]; + *row++ = col[0]; + if (image->bpp == 4) *row++ = col[3]; + } + } else { + if (fread(row, 1, n, f) != n) goto fail; + row += n; + } + } + i += n; + } + } + } + fclose(f); + return 0; +fail: + printf("Error: can't load '%s'\n", szName); + fclose(f); + return 1; +} + +int load_image(char* szName, Image* image) { + FILE* f; + + if ((f = fopen(szName, "rb")) != 0) { + unsigned int sign; + size_t res = fread(&sign, sizeof(sign), 1, f); + fclose(f); + + if (res == 1) { + if (sign == 0x474E5089) return load_png(szName, image); + else + return load_tga(szName, image); + } + } + + printf("Error: can't load '%s'\n", szName); + return 1; +} + +unsigned char find_common_coltype(std::vector& img) { + unsigned char coltype = img[0].type; + + for (size_t i = 1; i < img.size(); i++) { + if (img[0].ps != img[i].ps || memcmp(img[0].pl, img[i].pl, img[0].ps * 3) != 0) coltype = 6; + else if (img[0].ts != img[i].ts || memcmp(img[0].tr, img[i].tr, img[0].ts) != 0) + coltype = 6; + else if (img[i].type != 3) { + if (coltype != 3) coltype |= img[i].type; + else + coltype = 6; + } else if (coltype != 3) + coltype = 6; + } + return coltype; +} + +void up0to6(Image* image) { + image->type = 6; + image->bpp = 4; + unsigned int x, y; + unsigned char g, a; + unsigned char *sp, *dp; + unsigned int rowbytes = image->w * image->bpp; + unsigned char* dst = new unsigned char[image->h * rowbytes]; + + for (y = 0; y < image->h; y++) { + sp = image->rows[y]; + image->rows[y] = (y == 0) ? dst : image->rows[y - 1] + rowbytes; + dp = image->rows[y]; + for (x = 0; x < image->w; x++) { + g = *sp++; + a = (image->ts > 0 && image->tr[1] == g) ? 0 : 255; + *dp++ = g; + *dp++ = g; + *dp++ = g; + *dp++ = a; + } + } + delete[] image->p; + image->p = dst; +} + +void up2to6(Image* image) { + image->type = 6; + image->bpp = 4; + unsigned int x, y; + unsigned char r, g, b, a; + unsigned char *sp, *dp; + unsigned int rowbytes = image->w * image->bpp; + unsigned char* dst = new unsigned char[image->h * rowbytes]; + + for (y = 0; y < image->h; y++) { + sp = image->rows[y]; + image->rows[y] = (y == 0) ? dst : image->rows[y - 1] + rowbytes; + dp = image->rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + a = (image->ts > 0 && image->tr[1] == r && image->tr[3] == g && image->tr[5] == b) ? 0 : 255; + *dp++ = r; + *dp++ = g; + *dp++ = b; + *dp++ = a; + } + } + delete[] image->p; + image->p = dst; +} + +void up3to6(Image* image) { + image->type = 6; + image->bpp = 4; + unsigned int x, y; + unsigned char *sp, *dp; + unsigned int rowbytes = image->w * image->bpp; + unsigned char* dst = new unsigned char[image->h * rowbytes]; + + for (y = 0; y < image->h; y++) { + sp = image->rows[y]; + image->rows[y] = (y == 0) ? dst : image->rows[y - 1] + rowbytes; + dp = image->rows[y]; + for (x = 0; x < image->w; x++) { + *dp++ = image->pl[*sp].r; + *dp++ = image->pl[*sp].g; + *dp++ = image->pl[*sp].b; + *dp++ = image->tr[*sp++]; + } + } + delete[] image->p; + image->p = dst; +} + +void up4to6(Image* image) { + image->type = 6; + image->bpp = 4; + unsigned int x, y; + unsigned char *sp, *dp; + unsigned int rowbytes = image->w * image->bpp; + unsigned char* dst = new unsigned char[image->h * rowbytes]; + + for (y = 0; y < image->h; y++) { + sp = image->rows[y]; + image->rows[y] = (y == 0) ? dst : image->rows[y - 1] + rowbytes; + dp = image->rows[y]; + for (x = 0; x < image->w; x++) { + *dp++ = *sp; + *dp++ = *sp; + *dp++ = *sp++; + *dp++ = *sp++; + } + } + delete[] image->p; + image->p = dst; +} + +void up0to4(Image* image) { + image->type = 4; + image->bpp = 2; + unsigned int x, y; + unsigned char *sp, *dp; + unsigned int rowbytes = image->w * image->bpp; + unsigned char* dst = new unsigned char[image->h * rowbytes]; + + for (y = 0; y < image->h; y++) { + sp = image->rows[y]; + image->rows[y] = (y == 0) ? dst : image->rows[y - 1] + rowbytes; + dp = image->rows[y]; + for (x = 0; x < image->w; x++) { + *dp++ = *sp++; + *dp++ = 255; + } + } + delete[] image->p; + image->p = dst; +} + +void up0to2(Image* image) { + image->type = 2; + image->bpp = 3; + unsigned int x, y; + unsigned char *sp, *dp; + unsigned int rowbytes = image->w * image->bpp; + unsigned char* dst = new unsigned char[image->h * rowbytes]; + + for (y = 0; y < image->h; y++) { + sp = image->rows[y]; + image->rows[y] = (y == 0) ? dst : image->rows[y - 1] + rowbytes; + dp = image->rows[y]; + for (x = 0; x < image->w; x++) { + *dp++ = *sp; + *dp++ = *sp; + *dp++ = *sp++; + } + } + delete[] image->p; + image->p = dst; +} + +void optim_upconvert(Image* image, unsigned char coltype) { + if (image->type == 0 && coltype == 6) up0to6(image); + else if (image->type == 2 && coltype == 6) + up2to6(image); + else if (image->type == 3 && coltype == 6) + up3to6(image); + else if (image->type == 4 && coltype == 6) + up4to6(image); + else if (image->type == 0 && coltype == 4) + up0to4(image); + else if (image->type == 0 && coltype == 2) + up0to2(image); +} + +void optim_dirty_transp(Image* image) { + unsigned int x, y; + if (image->type == 6) { + for (y = 0; y < image->h; y++) { + unsigned char* sp = image->rows[y]; + for (x = 0; x < image->w; x++, sp += 4) { + if (sp[3] == 0) sp[0] = sp[1] = sp[2] = 0; + } + } + } else if (image->type == 4) { + for (y = 0; y < image->h; y++) { + unsigned char* sp = image->rows[y]; + for (x = 0; x < image->w; x++, sp += 2) { + if (sp[1] == 0) sp[0] = 0; + } + } + } +} + +int different(Image* image1, Image* image2) { + return memcmp(image1->p, image2->p, image1->w * image1->h * image1->bpp); +} + +void optim_duplicates(std::vector& img, unsigned int first) { + unsigned int i = first; + + while (++i < img.size()) { + if (different(&img[i - 1], &img[i])) continue; + + i--; + unsigned int num = img[i].delay_num; + unsigned int den = img[i].delay_den; + + img[i].free(); + img.erase(img.begin() + i); + + if (img[i].delay_den == den) img[i].delay_num += num; + else { + img[i].delay_num = num = num * img[i].delay_den + den * img[i].delay_num; + img[i].delay_den = den = den * img[i].delay_den; + while (num && den) { + if (num > den) num = num % den; + else + den = den % num; + } + num += den; + img[i].delay_num /= num; + img[i].delay_den /= num; + } + } +} + +typedef struct { + unsigned int num; + unsigned char r, g, b, a; +} COLORS; + +int cmp_colors(const void* arg1, const void* arg2) { + if (((COLORS*)arg1)->a != ((COLORS*)arg2)->a) + return (int)(((COLORS*)arg1)->a) - (int)(((COLORS*)arg2)->a); + + if (((COLORS*)arg1)->num != ((COLORS*)arg2)->num) + return (int)(((COLORS*)arg2)->num) - (int)(((COLORS*)arg1)->num); + + if (((COLORS*)arg1)->r != ((COLORS*)arg2)->r) + return (int)(((COLORS*)arg1)->r) - (int)(((COLORS*)arg2)->r); + + if (((COLORS*)arg1)->g != ((COLORS*)arg2)->g) + return (int)(((COLORS*)arg1)->g) - (int)(((COLORS*)arg2)->g); + + return (int)(((COLORS*)arg1)->b) - (int)(((COLORS*)arg2)->b); +} + +void down6(std::vector& img) { + unsigned int i, k, x, y; + unsigned char* sp; + unsigned char* dp; + unsigned char r, g, b, a; + int simple_transp = 1; + int full_transp = 0; + int grayscale = 1; + unsigned char cube[4096]; + unsigned char gray[256]; + COLORS col[256]; + unsigned int colors = 0; + Image* image = &img[0]; + + memset(&cube, 0, sizeof(cube)); + memset(&gray, 0, sizeof(gray)); + + for (i = 0; i < 256; i++) { + col[i].num = 0; + col[i].r = col[i].g = col[i].b = i; + col[i].a = image->tr[i] = 255; + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + a = *sp++; + + if (a != 0) { + if (a != 255) simple_transp = 0; + else if (((r | g | b) & 15) == 0) + cube[(r << 4) + g + (b >> 4)] = 1; + + if (r != g || g != b) grayscale = 0; + else + gray[r] = 1; + } else + full_transp = 1; + + if (colors <= 256) { + int found = 0; + for (k = 0; k < colors; k++) + if (col[k].r == r && col[k].g == g && col[k].b == b && col[k].a == a) { + found = 1; + col[k].num++; + break; + } + if (found == 0) { + if (colors < 256) { + col[colors].num++; + col[colors].r = r; + col[colors].g = g; + col[colors].b = b; + col[colors].a = a; + } + colors++; + } + } + } + } + } + + if (colors <= 256) { + if (grayscale && simple_transp && colors > 128) /* 6 -> 0 */ + { + image->type = 0; + image->bpp = 1; + unsigned char t = 0; + + for (i = 0; i < 256; i++) + if (gray[i] == 0) { + image->tr[0] = 0; + image->tr[1] = t = i; + image->ts = 2; + break; + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++, sp += 4) { + *dp++ = (sp[3] == 0) ? t : sp[0]; + } + } + } + } else /* 6 -> 3 */ + { + image->type = 3; + image->bpp = 1; + + if (full_transp == 0 && colors < 256) col[colors++].a = 0; + + qsort(&col[0], colors, sizeof(COLORS), cmp_colors); + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + a = *sp++; + for (k = 0; k < colors; k++) + if (col[k].r == r && col[k].g == g && col[k].b == b && col[k].a == a) break; + *dp++ = k; + } + } + } + + image->ps = colors; + for (i = 0; i < colors; i++) { + image->pl[i].r = col[i].r; + image->pl[i].g = col[i].g; + image->pl[i].b = col[i].b; + image->tr[i] = col[i].a; + if (image->tr[i] != 255) image->ts = i + 1; + } + } + } else if (grayscale) /* 6 -> 4 */ + { + image->type = 4; + image->bpp = 2; + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++, sp += 4) { + *dp++ = sp[2]; + *dp++ = sp[3]; + } + } + } + } else if (simple_transp) /* 6 -> 2 */ + { + for (i = 0; i < 4096; i++) + if (cube[i] == 0) { + image->tr[0] = 0; + image->tr[1] = (i >> 4) & 0xF0; + image->tr[2] = 0; + image->tr[3] = i & 0xF0; + image->tr[4] = 0; + image->tr[5] = (i << 4) & 0xF0; + image->ts = 6; + break; + } + if (image->ts != 0) { + image->type = 2; + image->bpp = 3; + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + a = *sp++; + if (a == 0) { + *dp++ = image->tr[1]; + *dp++ = image->tr[3]; + *dp++ = image->tr[5]; + } else { + *dp++ = r; + *dp++ = g; + *dp++ = b; + } + } + } + } + } + } +} + +void down2(std::vector& img) { + unsigned int i, k, x, y; + unsigned char* sp; + unsigned char* dp; + unsigned char r, g, b, a; + int full_transp = 0; + int grayscale = 1; + unsigned char gray[256]; + COLORS col[256]; + unsigned int colors = 0; + Image* image = &img[0]; + + memset(&gray, 0, sizeof(gray)); + + for (i = 0; i < 256; i++) { + col[i].num = 0; + col[i].r = col[i].g = col[i].b = i; + col[i].a = 255; + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + a = (image->ts > 0 && image->tr[1] == r && image->tr[3] == g && image->tr[5] == b) ? 0 + : 255; + + if (a != 0) { + if (r != g || g != b) grayscale = 0; + else + gray[r] = 1; + } else + full_transp = 1; + + if (colors <= 256) { + int found = 0; + for (k = 0; k < colors; k++) + if (col[k].r == r && col[k].g == g && col[k].b == b && col[k].a == a) { + found = 1; + col[k].num++; + break; + } + if (found == 0) { + if (colors < 256) { + col[colors].num++; + col[colors].r = r; + col[colors].g = g; + col[colors].b = b; + col[colors].a = a; + } + colors++; + } + } + } + } + } + + if (colors <= 256) { + if (grayscale && colors > 128) /* 2 -> 0 */ + { + image->type = 0; + image->bpp = 1; + unsigned char t = 0; + int ts = 0; + + for (i = 0; i < 256; i++) { + if (gray[i] == 0) { + t = i; + ts = 2; + break; + } + } + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++, sp += 3) { + *dp++ = (image->ts > 0 && image->tr[1] == sp[0] && image->tr[3] == sp[1] && + image->tr[5] == sp[2]) + ? t + : sp[0]; + } + } + } + if (ts > 0) { + image->tr[0] = 0; + image->tr[1] = t; + image->ts = ts; + } + } else /* 2 -> 3 */ + { + image->type = 3; + image->bpp = 1; + + if (full_transp == 0 && colors < 256) col[colors++].a = 0; + + qsort(&col[0], colors, sizeof(COLORS), cmp_colors); + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + a = (image->ts > 0 && image->tr[1] == r && image->tr[3] == g && image->tr[5] == b) + ? 0 + : 255; + for (k = 0; k < colors; k++) + if (col[k].r == r && col[k].g == g && col[k].b == b && col[k].a == a) break; + *dp++ = k; + } + } + } + + image->ps = colors; + for (i = 0; i < colors; i++) { + image->pl[i].r = col[i].r; + image->pl[i].g = col[i].g; + image->pl[i].b = col[i].b; + image->tr[i] = col[i].a; + if (image->tr[i] != 255) image->ts = i + 1; + } + } + } +} + +void down4(std::vector& img) { + unsigned int i, k, x, y; + unsigned char* sp; + unsigned char* dp; + unsigned char g, a; + int simple_transp = 1; + int full_transp = 0; + unsigned char gray[256]; + COLORS col[256]; + unsigned int colors = 0; + Image* image = &img[0]; + + memset(&gray, 0, sizeof(gray)); + + for (i = 0; i < 256; i++) { + col[i].num = 0; + col[i].r = col[i].g = col[i].b = i; + col[i].a = image->tr[i] = 255; + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + g = *sp++; + a = *sp++; + + if (a != 0) { + if (a != 255) simple_transp = 0; + else + gray[g] = 1; + } else + full_transp = 1; + + if (colors <= 256) { + int found = 0; + for (k = 0; k < colors; k++) + if (col[k].g == g && col[k].a == a) { + found = 1; + col[k].num++; + break; + } + if (found == 0) { + if (colors < 256) { + col[colors].num++; + col[colors].r = g; + col[colors].g = g; + col[colors].b = g; + col[colors].a = a; + } + colors++; + } + } + } + } + } + + if (simple_transp && colors <= 256) /* 4 -> 0 */ + { + image->type = 0; + image->bpp = 1; + unsigned char t = 0; + + for (i = 0; i < 256; i++) + if (gray[i] == 0) { + image->tr[0] = 0; + image->tr[1] = t = i; + image->ts = 2; + break; + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++, sp += 2) { + *dp++ = (sp[1] == 0) ? t : sp[0]; + } + } + } + } else if (colors <= 256) /* 4 -> 3 */ + { + image->type = 3; + image->bpp = 1; + + if (full_transp == 0 && colors < 256) col[colors++].a = 0; + + qsort(&col[0], colors, sizeof(COLORS), cmp_colors); + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = dp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + g = *sp++; + a = *sp++; + for (k = 0; k < colors; k++) + if (col[k].g == g && col[k].a == a) break; + *dp++ = k; + } + } + } + + image->ps = colors; + for (i = 0; i < colors; i++) { + image->pl[i].r = col[i].r; + image->pl[i].g = col[i].g; + image->pl[i].b = col[i].b; + image->tr[i] = col[i].a; + if (image->tr[i] != 255) image->ts = i + 1; + } + } +} + +void down3(std::vector& img) { + unsigned int i, x, y; + unsigned char* sp; + unsigned char* dp; + int c; + int simple_transp = 1; + int grayscale = 1; + unsigned char gray[256]; + COLORS col[256]; + Image* image = &img[0]; + + memset(&gray, 0, sizeof(gray)); + + for (c = 0; c < 256; c++) { + col[c].num = 0; + if (c < image->ps) { + col[c].r = image->pl[c].r; + col[c].g = image->pl[c].g; + col[c].b = image->pl[c].b; + col[c].a = image->tr[c]; + } else { + col[c].r = col[c].g = col[c].b = c; + col[c].a = 255; + } + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) col[*sp++].num++; + } + } + + for (i = 0; i < 256; i++) + if (col[i].num != 0) { + if (col[i].a != 0) { + if (col[i].a != 255) simple_transp = 0; + else if (col[i].r != col[i].g || col[i].g != col[i].b) + grayscale = 0; + else + gray[col[i].g] = 1; + } + } + + if (grayscale && simple_transp) /* 3 -> 0 */ + { + image->type = 0; + image->bpp = 1; + unsigned char t = 0; + int ts = 0; + + for (i = 0; i < 256; i++) { + if (gray[i] == 0) { + t = i; + ts = 2; + break; + } + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + dp = img[i].rows[y]; + for (x = 0; x < image->w; x++, dp++) { + *dp = (col[*dp].a == 0) ? t : image->pl[*dp].g; + } + } + } + image->ps = 0; + image->ts = 0; + if (ts > 0) { + image->tr[0] = 0; + image->tr[1] = t; + image->ts = ts; + } + } +} + +void optim_downconvert(std::vector& img) { + if (img[0].type == 6) down6(img); + else if (img[0].type == 2) + down2(img); + else if (img[0].type == 4) + down4(img); + else if (img[0].type == 3) + down3(img); +} + +void optim_palette(std::vector& img) { + unsigned int i, x, y; + unsigned char* sp; + unsigned char r, g, b, a; + int c; + int full_transp = 0; + COLORS col[256]; + Image* image = &img[0]; + + for (c = 0; c < 256; c++) { + col[c].num = 0; + if (c < image->ps) { + col[c].r = image->pl[c].r; + col[c].g = image->pl[c].g; + col[c].b = image->pl[c].b; + col[c].a = image->tr[c]; + } else { + col[c].r = col[c].g = col[c].b = c; + col[c].a = 255; + } + } + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) col[*sp++].num++; + } + } + + for (i = 0; i < 256; i++) { + if (col[i].num != 0 && col[i].a == 0) { + full_transp = 1; + break; + } + } + + for (i = 0; i < 256; i++) { + if (col[i].num == 0) { + col[i].a = 255; + if (full_transp == 0) { + col[i].a = 0; + full_transp = 1; + } + } + } + + qsort(&col[0], 256, sizeof(COLORS), cmp_colors); + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = image->pl[*sp].r; + g = image->pl[*sp].g; + b = image->pl[*sp].b; + a = image->tr[*sp]; + for (c = 0; c < image->ps; c++) + if (col[c].r == r && col[c].g == g && col[c].b == b && col[c].a == a) break; + *sp++ = c; + } + } + } + + for (i = 0; i < 256; i++) { + image->pl[i].r = col[i].r; + image->pl[i].g = col[i].g; + image->pl[i].b = col[i].b; + image->tr[i] = col[i].a; + if (col[i].num != 0) image->ps = i + 1; + if (image->tr[i] != 255) image->ts = i + 1; + } +} + +void add_transp2(std::vector& img) { + unsigned int i, x, y; + unsigned char* sp; + unsigned char r, g, b; + unsigned char cube[4096]; + Image* image = &img[0]; + + memset(&cube, 0, sizeof(cube)); + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) { + r = *sp++; + g = *sp++; + b = *sp++; + if (((r | g | b) & 15) == 0) cube[(r << 4) + g + (b >> 4)] = 1; + } + } + } + + for (i = 0; i < 4096; i++) + if (cube[i] == 0) { + image->tr[0] = 0; + image->tr[1] = (i >> 4) & 0xF0; + image->tr[2] = 0; + image->tr[3] = i & 0xF0; + image->tr[4] = 0; + image->tr[5] = (i << 4) & 0xF0; + image->ts = 6; + break; + } +} + +void add_transp0(std::vector& img) { + unsigned int i, x, y; + unsigned char* sp; + unsigned char gray[256]; + Image* image = &img[0]; + + memset(&gray, 0, sizeof(gray)); + + for (i = 0; i < img.size(); i++) { + for (y = 0; y < image->h; y++) { + sp = img[i].rows[y]; + for (x = 0; x < image->w; x++) gray[*sp++] = 1; + } + } + + for (i = 0; i < 256; i++) + if (gray[i] == 0) { + image->tr[0] = 0; + image->tr[1] = i; + image->ts = 2; + break; + } +} + +void optim_add_transp(std::vector& img) { + if (img[0].ts == 0) { + if (img[0].type == 2) add_transp2(img); + else if (img[0].type == 0) + add_transp0(img); + } +} diff --git a/viewer/src/utils/apng/image.h b/viewer/src/utils/apng/image.h new file mode 100644 index 0000000000..6f107ea2f2 --- /dev/null +++ b/viewer/src/utils/apng/image.h @@ -0,0 +1,86 @@ +/* Image library + * + * Copyright (c) 2016 Max Stepin + * maxst at users.sourceforge.net + * + * zlib license + * ------------ + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + +#ifndef IMAGE_H +#define IMAGE_H +#include +#include + +struct rgb { + unsigned char r, g, b; +}; + +struct Image { + typedef unsigned char* ROW; + unsigned int w, h, bpp, type; + int ps, ts; + rgb pl[256]; + unsigned char tr[256]; + unsigned int delay_num, delay_den; + unsigned char* p; + ROW* rows; + Image() : w(0), h(0), bpp(0), type(0), ps(0), ts(0), delay_num(1), delay_den(10), p(0), rows(0) { + memset(pl, 255, sizeof(pl)); + memset(tr, 255, sizeof(tr)); + } + ~Image() { + } + void init(unsigned int w1, unsigned int h1, unsigned int bpp1, unsigned int type1) { + w = w1; + h = h1; + bpp = bpp1; + type = type1; + int rowbytes = w * bpp; + delete[] rows; + delete[] p; + rows = new ROW[h]; + rows[0] = p = new unsigned char[h * rowbytes]; + for (unsigned int j = 1; j < h; j++) rows[j] = rows[j - 1] + rowbytes; + } + void init(unsigned int w, unsigned int h, Image* image) { + init(w, h, image->bpp, image->type); + if ((ps = image->ps) != 0) memcpy(&pl[0], &image->pl[0], ps * 3); + if ((ts = image->ts) != 0) memcpy(&tr[0], &image->tr[0], ts); + } + void init(Image* image) { + init(image->w, image->h, image); + } + void free() { + delete[] rows; + delete[] p; + } +}; + +int load_image(char* szName, Image* image); +unsigned char find_common_coltype(std::vector& img); +void optim_upconvert(Image* image, unsigned char coltype); +void optim_duplicates(std::vector& img, unsigned int first); +void optim_dirty_transp(Image* image); +void optim_downconvert(std::vector& img); +void optim_palette(std::vector& img); +void optim_add_transp(std::vector& img); + +#endif /* IMAGE_H */