Add support for .qoi file format (fix #3121)

This commit is contained in:
David Capello 2023-01-04 12:06:20 -03:00
parent 2e02bd307e
commit d3aac6a1cd
10 changed files with 233 additions and 6 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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)
```

View File

@ -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)

View File

@ -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());

183
src/app/file/qoi_format.cpp Normal file
View File

@ -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; y<desc.height; ++y) {
auto dst = (uint32_t*)image->getPixelAddress(0, y);
switch (desc.channels) {
case 4:
for (int x=0; x<desc.width; ++x, ++dst) {
*dst = doc::rgba(src[0], src[1], src[2], src[3]);
src += 4;
}
break;
case 3:
for (int x=0; x<desc.width; ++x, ++dst) {
*dst = doc::rgba(src[0], src[1], src[2], 255);
src += 3;
}
break;
}
}
QOI_FREE(pixels);
if (desc.channels == 4)
fop->sequenceSetHasAlpha(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; y<desc.height; ++y) {
auto src = (uint32_t*)image->getPixelAddress(0, y);
switch (desc.channels) {
case 4:
for (int x=0; x<desc.width; ++x, ++src) {
uint32_t c = *src;
dst[0] = doc::rgba_getr(c);
dst[1] = doc::rgba_getg(c);
dst[2] = doc::rgba_getb(c);
dst[3] = doc::rgba_geta(c);
dst += 4;
}
break;
case 3:
for (int x=0; x<desc.width; ++x, ++src) {
uint32_t c = *src;
dst[0] = doc::rgba_getr(c);
dst[1] = doc::rgba_getg(c);
dst[2] = doc::rgba_getb(c);
dst += 3;
}
break;
}
}
int size = 0;
auto encoded = qoi_encode(pixels, &desc, &size);
QOI_FREE(pixels);
if (!encoded)
return false;
fwrite(encoded, 1, size, f);
QOI_FREE(encoded);
if (ferror(handle.get())) {
fop->setError("Error writing file.\n");
return false;
}
else {
return true;
}
}
#endif // ENABLE_SAVE
} // namespace app

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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)

1
third_party/qoi vendored Submodule

@ -0,0 +1 @@
Subproject commit c3dcfe780bf28f4a59bca7f0a60e2e18b0acf68f