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 */