From d3aac6a1cdcbe0a36cecd114644a537cbce140e1 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 4 Jan 2023 12:06:20 -0300 Subject: [PATCH] Add support for .qoi file format (fix #3121) --- .gitmodules | 3 + docs/LICENSES.md | 24 ++++ src/app/CMakeLists.txt | 4 +- src/app/file/file_formats_manager.cpp | 4 +- src/app/file/qoi_format.cpp | 183 ++++++++++++++++++++++++++ src/dio/LICENSE.txt | 2 +- src/dio/detect_format.cpp | 9 +- src/dio/file_format.h | 3 +- third_party/CMakeLists.txt | 6 +- third_party/qoi | 1 + 10 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 src/app/file/qoi_format.cpp create mode 160000 third_party/qoi diff --git a/.gitmodules b/.gitmodules index f053e6d23..f89fde895 100644 --- a/.gitmodules +++ b/.gitmodules @@ -81,3 +81,6 @@ [submodule "src/psd"] path = src/psd url = https://github.com/aseprite/psd.git +[submodule "third_party/qoi"] + path = third_party/qoi + url = https://github.com/aseprite/qoi.git diff --git a/docs/LICENSES.md b/docs/LICENSES.md index 2b2f0fdcb..5be4b2a76 100644 --- a/docs/LICENSES.md +++ b/docs/LICENSES.md @@ -1009,6 +1009,30 @@ possible. They may also add themselves to the list below. */ ``` +# [qoi](https://github.com/phoboslab/qoi) + +``` +Copyright (c) 2022 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + # [Sentry](https://sentry.io) ``` diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 74c238f55..511337c36 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -136,6 +136,7 @@ set(file_formats file/jpeg_format.cpp file/pcx_format.cpp file/png_format.cpp + file/qoi_format.cpp file/svg_format.cpp file/tga_format.cpp) if(ENABLE_WEBP) @@ -719,7 +720,8 @@ target_link_libraries(app-lib json11 archive_static fmt - tinyexpr) + tinyexpr + qoi) if(ENABLE_PSD) target_link_libraries(app-lib psd) diff --git a/src/app/file/file_formats_manager.cpp b/src/app/file/file_formats_manager.cpp index a1ec6b615..887119e69 100644 --- a/src/app/file/file_formats_manager.cpp +++ b/src/app/file/file_formats_manager.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2023 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -30,6 +30,7 @@ extern FileFormat* CreateIcoFormat(); extern FileFormat* CreateJpegFormat(); extern FileFormat* CreatePcxFormat(); extern FileFormat* CreatePngFormat(); +extern FileFormat* CreateQoiFormat(); extern FileFormat* CreateSvgFormat(); extern FileFormat* CreateTgaFormat(); @@ -75,6 +76,7 @@ FileFormatsManager::FileFormatsManager() registerFormat(CreatePsdFormat()); #endif + registerFormat(CreateQoiFormat()); registerFormat(CreateSvgFormat()); registerFormat(CreateTgaFormat()); diff --git a/src/app/file/qoi_format.cpp b/src/app/file/qoi_format.cpp new file mode 100644 index 000000000..ced645b6f --- /dev/null +++ b/src/app/file/qoi_format.cpp @@ -0,0 +1,183 @@ +// Aseprite +// Copyright (C) 2023 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/file/file.h" +#include "app/file/file_format.h" +#include "base/file_handle.h" + +#define QOI_NO_STDIO +#define QOI_IMPLEMENTATION +#include "qoi.h" + +namespace app { + +using namespace base; + +class QoiFormat : public FileFormat { + const char* onGetName() const override { + return "qoi"; + } + + void onGetExtensions(base::paths& exts) const override { + exts.push_back("qoi"); + } + + dio::FileFormat onGetDioFormat() const override { + return dio::FileFormat::QOI_IMAGE; + } + + int onGetFlags() const override { + return + FILE_SUPPORT_LOAD | + FILE_SUPPORT_SAVE | + FILE_SUPPORT_RGB | + FILE_SUPPORT_RGBA | + FILE_SUPPORT_SEQUENCES | + FILE_ENCODE_ABSTRACT_IMAGE; + } + + bool onLoad(FileOp* fop) override; +#ifdef ENABLE_SAVE + bool onSave(FileOp* fop) override; +#endif +}; + +FileFormat* CreateQoiFormat() +{ + return new QoiFormat; +} + +bool QoiFormat::onLoad(FileOp* fop) +{ + FileHandle handle(open_file_with_exception(fop->filename(), "rb")); + FILE* f = handle.get(); + + fseek(f, 0, SEEK_END); + auto size = ftell(f); + if (size <= 0) + return false; + fseek(f, 0, SEEK_SET); + + auto data = QOI_MALLOC(size); + if (!data) + return false; + + qoi_desc desc; + auto bytes_read = fread(data, 1, size, f); + auto pixels = qoi_decode(data, bytes_read, &desc, 0); + QOI_FREE(data); + if (!pixels) + return false; + + ImageRef image = fop->sequenceImage(IMAGE_RGB, + desc.width, + desc.height); + if (!image) + return false; + + auto src = (const uint8_t*)pixels; + for (int y=0; ygetPixelAddress(0, y); + switch (desc.channels) { + case 4: + for (int x=0; xsequenceSetHasAlpha(true); + + if (ferror(handle.get())) { + fop->setError("Error reading file.\n"); + return false; + } + else { + return true; + } +} + +#ifdef ENABLE_SAVE + +bool QoiFormat::onSave(FileOp* fop) +{ + const FileAbstractImage* img = fop->abstractImage(); + FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb")); + FILE* f = handle.get(); + doc::ImageRef image = img->getScaledImage(); + + qoi_desc desc; + desc.width = img->width(); + desc.height = img->height(); + desc.channels = (img->needAlpha() ? 4: 3); + desc.colorspace = QOI_LINEAR; // TODO QOI_SRGB + + auto pixels = (uint8_t*)QOI_MALLOC(desc.width * desc.height * desc.channels); + if (!pixels) + return false; + + auto dst = pixels; + for (int y=0; ygetPixelAddress(0, y); + switch (desc.channels) { + case 4: + for (int x=0; xsetError("Error writing file.\n"); + return false; + } + else { + return true; + } +} + +#endif // ENABLE_SAVE + +} // namespace app diff --git a/src/dio/LICENSE.txt b/src/dio/LICENSE.txt index 3fa39272d..e7e9892ac 100644 --- a/src/dio/LICENSE.txt +++ b/src/dio/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2018-2022 Igara Studio S.A. +Copyright (c) 2018-2023 Igara Studio S.A. Copyright (c) 2016-2018 David Capello Permission is hereby granted, free of charge, to any person obtaining diff --git a/src/dio/detect_format.cpp b/src/dio/detect_format.cpp index 8f7236eff..4cc0aace4 100644 --- a/src/dio/detect_format.cpp +++ b/src/dio/detect_format.cpp @@ -1,5 +1,5 @@ // Aseprite Document IO Library -// Copyright (c) 2021 Igara Studio S.A. +// Copyright (c) 2021-2023 Igara Studio S.A. // Copyright (c) 2016-2018 David Capello // // This file is released under the terms of the MIT license. @@ -24,6 +24,7 @@ #define WEBP_STAMP_1 "RIFF" // "RIFFnnnnWEBP" #define WEBP_STAMP_2 "WEBP" #define PSD_STAMP "8BPS" +#define QOI_STAMP "qoif" namespace dio { @@ -69,6 +70,9 @@ FileFormat detect_format_by_file_content_bytes(const uint8_t* buf, if (std::strncmp((const char*)buf, PSD_STAMP, 4) == 0) return FileFormat::PSD_IMAGE; + if (std::strncmp((const char*)buf, QOI_STAMP, 4) == 0) + return FileFormat::QOI_IMAGE; + if (IS_MAGIC_WORD(4, ASE_MAGIC_NUMBER)) return FileFormat::ASE_ANIMATION; @@ -164,6 +168,9 @@ FileFormat detect_format_by_file_extension(const std::string& filename) ext == "psb") return FileFormat::PSD_IMAGE; + if (ext == "qoi") + return FileFormat::QOI_IMAGE; + return FileFormat::UNKNOWN; } diff --git a/src/dio/file_format.h b/src/dio/file_format.h index 457827ac8..c58fa8b7d 100644 --- a/src/dio/file_format.h +++ b/src/dio/file_format.h @@ -1,5 +1,5 @@ // Aseprite Document IO Library -// Copyright (c) 2021 Igara Studio S.A. +// Copyright (c) 2021-2023 Igara Studio S.A. // Copyright (c) 2016-2017 David Capello // // This file is released under the terms of the MIT license. @@ -34,6 +34,7 @@ enum class FileFormat { WEBP_ANIMATION, CSS_STYLE, PSD_IMAGE, + QOI_IMAGE, }; } // namespace dio diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 38b91ba9e..42c5a9014 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,5 +1,5 @@ # Aseprite -# Copyright (C) 2021-2022 Igara Studio S.A. +# Copyright (C) 2021-2023 Igara Studio S.A. # Copyright (C) 2001-2018 David Capello include_directories(.) @@ -210,3 +210,7 @@ if(ENABLE_SCRIPTING) endif() endif() + +# qoi +add_library(qoi INTERFACE) +target_include_directories(qoi INTERFACE qoi) diff --git a/third_party/qoi b/third_party/qoi new file mode 160000 index 000000000..c3dcfe780 --- /dev/null +++ b/third_party/qoi @@ -0,0 +1 @@ +Subproject commit c3dcfe780bf28f4a59bca7f0a60e2e18b0acf68f