Skip to content

Commit

Permalink
QOI Image Support
Browse files Browse the repository at this point in the history
  • Loading branch information
basicer committed Sep 5, 2024
1 parent b6223c0 commit 6fe9e86
Show file tree
Hide file tree
Showing 17 changed files with 1,204 additions and 4 deletions.
5 changes: 5 additions & 0 deletions COPYRIGHT.txt
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,11 @@ Comment: Quite OK Audio Format
Copyright: 2023, Dominic Szablewski
License: Expat

Files: ./thirdparty/misc/qoi.h
Comment: Quite OK Image Format
Copyright: 2024, Dominic Szablewski
License: Expat

Files: ./thirdparty/misc/r128.c
./thirdparty/misc/r128.h
Comment: r128 library
Expand Down
30 changes: 30 additions & 0 deletions core/io/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@ const char *Image::format_names[Image::FORMAT_MAX] = {

SavePNGFunc Image::save_png_func = nullptr;
SaveJPGFunc Image::save_jpg_func = nullptr;
SaveQOIFunc Image::save_qoi_func = nullptr;
SaveEXRFunc Image::save_exr_func = nullptr;

SavePNGBufferFunc Image::save_png_buffer_func = nullptr;
SaveEXRBufferFunc Image::save_exr_buffer_func = nullptr;
SaveJPGBufferFunc Image::save_jpg_buffer_func = nullptr;
SaveQOIBufferFunc Image::save_qoi_buffer_func = nullptr;

SaveWebPFunc Image::save_webp_func = nullptr;
SaveWebPBufferFunc Image::save_webp_buffer_func = nullptr;
Expand Down Expand Up @@ -2616,6 +2618,14 @@ Error Image::save_jpg(const String &p_path, float p_quality) const {
return save_jpg_func(p_path, Ref<Image>((Image *)this), p_quality);
}

Error Image::save_qoi(const String &p_path) const {
if (save_qoi_func == nullptr) {
return ERR_UNAVAILABLE;
}

return save_qoi_func(p_path, Ref<Image>((Image *)this));
}

Vector<uint8_t> Image::save_png_to_buffer() const {
if (save_png_buffer_func == nullptr) {
return Vector<uint8_t>();
Expand All @@ -2624,6 +2634,14 @@ Vector<uint8_t> Image::save_png_to_buffer() const {
return save_png_buffer_func(Ref<Image>((Image *)this));
}

Vector<uint8_t> Image::save_qoi_to_buffer() const {
if (save_qoi_buffer_func == nullptr) {
return Vector<uint8_t>();
}

return save_qoi_buffer_func(Ref<Image>((Image *)this));
}

Vector<uint8_t> Image::save_jpg_to_buffer(float p_quality) const {
if (save_jpg_buffer_func == nullptr) {
return Vector<uint8_t>();
Expand Down Expand Up @@ -3129,6 +3147,7 @@ ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_bmp_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_qoi_mem_loader_func = nullptr;
ScalableImageMemLoadFunc Image::_svg_scalable_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_ktx_mem_loader_func = nullptr;

Expand Down Expand Up @@ -3567,6 +3586,8 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_png_to_buffer"), &Image::save_png_to_buffer);
ClassDB::bind_method(D_METHOD("save_jpg", "path", "quality"), &Image::save_jpg, DEFVAL(0.75));
ClassDB::bind_method(D_METHOD("save_jpg_to_buffer", "quality"), &Image::save_jpg_to_buffer, DEFVAL(0.75));
ClassDB::bind_method(D_METHOD("save_qoi", "path"), &Image::save_qoi);
ClassDB::bind_method(D_METHOD("save_qoi_to_buffer"), &Image::save_qoi_to_buffer);
ClassDB::bind_method(D_METHOD("save_exr", "path", "grayscale"), &Image::save_exr, DEFVAL(false));
ClassDB::bind_method(D_METHOD("save_exr_to_buffer", "grayscale"), &Image::save_exr_to_buffer, DEFVAL(false));
ClassDB::bind_method(D_METHOD("save_webp", "path", "lossy", "quality"), &Image::save_webp, DEFVAL(false), DEFVAL(0.75f));
Expand Down Expand Up @@ -3621,6 +3642,7 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_webp_from_buffer", "buffer"), &Image::load_webp_from_buffer);
ClassDB::bind_method(D_METHOD("load_tga_from_buffer", "buffer"), &Image::load_tga_from_buffer);
ClassDB::bind_method(D_METHOD("load_bmp_from_buffer", "buffer"), &Image::load_bmp_from_buffer);
ClassDB::bind_method(D_METHOD("load_qoi_from_buffer", "buffer"), &Image::load_qoi_from_buffer);
ClassDB::bind_method(D_METHOD("load_ktx_from_buffer", "buffer"), &Image::load_ktx_from_buffer);

ClassDB::bind_method(D_METHOD("load_svg_from_buffer", "buffer", "scale"), &Image::load_svg_from_buffer, DEFVAL(1.0));
Expand Down Expand Up @@ -4034,6 +4056,14 @@ Error Image::load_bmp_from_buffer(const Vector<uint8_t> &p_array) {
return _load_from_buffer(p_array, _bmp_mem_loader_func);
}

Error Image::load_qoi_from_buffer(const Vector<uint8_t> &p_array) {
ERR_FAIL_NULL_V_MSG(
_qoi_mem_loader_func,
ERR_UNAVAILABLE,
"The QOI module isn't enabled. Recompile the Godot editor or export template binary with the `module_qoi_enabled=yes` SCons option.");
return _load_from_buffer(p_array, _qoi_mem_loader_func);
}

Error Image::load_svg_from_buffer(const Vector<uint8_t> &p_array, float scale) {
ERR_FAIL_NULL_V_MSG(
_svg_scalable_mem_loader_func,
Expand Down
8 changes: 8 additions & 0 deletions core/io/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@
class Image;

typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img);
typedef Error (*SaveQOIFunc)(const String &p_path, const Ref<Image> &p_img);
typedef Vector<uint8_t> (*SavePNGBufferFunc)(const Ref<Image> &p_img);
typedef Error (*SaveJPGFunc)(const String &p_path, const Ref<Image> &p_img, float p_quality);
typedef Vector<uint8_t> (*SaveJPGBufferFunc)(const Ref<Image> &p_img, float p_quality);
typedef Vector<uint8_t> (*SaveQOIBufferFunc)(const Ref<Image> &p_img);
typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size);
typedef Ref<Image> (*ScalableImageMemLoadFunc)(const uint8_t *p_data, int p_size, float p_scale);
typedef Error (*SaveWebPFunc)(const String &p_path, const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
Expand All @@ -60,11 +62,13 @@ class Image : public Resource {

public:
static SavePNGFunc save_png_func;
static SaveQOIFunc save_qoi_func;
static SaveJPGFunc save_jpg_func;
static SaveEXRFunc save_exr_func;
static SavePNGBufferFunc save_png_buffer_func;
static SaveEXRBufferFunc save_exr_buffer_func;
static SaveJPGBufferFunc save_jpg_buffer_func;
static SaveQOIBufferFunc save_qoi_buffer_func;
static SaveWebPFunc save_webp_func;
static SaveWebPBufferFunc save_webp_buffer_func;

Expand Down Expand Up @@ -150,6 +154,7 @@ class Image : public Resource {
static ImageMemLoadFunc _webp_mem_loader_func;
static ImageMemLoadFunc _tga_mem_loader_func;
static ImageMemLoadFunc _bmp_mem_loader_func;
static ImageMemLoadFunc _qoi_mem_loader_func;
static ScalableImageMemLoadFunc _svg_scalable_mem_loader_func;
static ImageMemLoadFunc _ktx_mem_loader_func;

Expand Down Expand Up @@ -314,8 +319,10 @@ class Image : public Resource {
Error load(const String &p_path);
static Ref<Image> load_from_file(const String &p_path);
Error save_png(const String &p_path) const;
Error save_qoi(const String &p_path) const;
Error save_jpg(const String &p_path, float p_quality = 0.75) const;
Vector<uint8_t> save_png_to_buffer() const;
Vector<uint8_t> save_qoi_to_buffer() const;
Vector<uint8_t> save_jpg_to_buffer(float p_quality = 0.75) const;
Vector<uint8_t> save_exr_to_buffer(bool p_grayscale = false) const;
Error save_exr(const String &p_path, bool p_grayscale = false) const;
Expand Down Expand Up @@ -413,6 +420,7 @@ class Image : public Resource {
Error load_tga_from_buffer(const Vector<uint8_t> &p_array);
Error load_bmp_from_buffer(const Vector<uint8_t> &p_array);
Error load_ktx_from_buffer(const Vector<uint8_t> &p_array);
Error load_qoi_from_buffer(const Vector<uint8_t> &p_array);

Error load_svg_from_buffer(const Vector<uint8_t> &p_array, float scale = 1.0);
Error load_svg_from_string(const String &p_svg_str, float scale = 1.0);
Expand Down
20 changes: 20 additions & 0 deletions doc/classes/Image.xml
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,13 @@
Loads an image from the binary contents of a PNG file.
</description>
</method>
<method name="load_qoi_from_buffer">
<return type="int" enum="Error" />
<param index="0" name="buffer" type="PackedByteArray" />
<description>
Loads an image from the binary contents of a [url=https://qoiformat.org/]QOI[/url] file.
</description>
</method>
<method name="load_svg_from_buffer">
<return type="int" enum="Error" />
<param index="0" name="buffer" type="PackedByteArray" />
Expand Down Expand Up @@ -504,6 +511,19 @@
Saves the image as a PNG file to a byte array.
</description>
</method>
<method name="save_qoi" qualifiers="const">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
Saves the image as a [url=https://qoiformat.org/]QOI[/url] file to the file at [param path].
</description>
</method>
<method name="save_qoi_to_buffer" qualifiers="const">
<return type="PackedByteArray" />
<description>
Saves the image as a [url=https://qoiformat.org/]QOI[/url] file to a byte array.
</description>
</method>
<method name="save_webp" qualifiers="const">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
Expand Down
13 changes: 13 additions & 0 deletions modules/qoi/SCsub
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python

Import("env")
Import("env_modules")

env_qoi = env_modules.Clone()

# Godot source files

module_obj = []

env_qoi.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
6 changes: 6 additions & 0 deletions modules/qoi/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def can_build(env, platform):
return True


def configure(env):
pass
105 changes: 105 additions & 0 deletions modules/qoi/image_loader_qoi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**************************************************************************/
/* image_loader_qoi.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/

#include "image_loader_qoi.h"

#include "core/io/file_access_memory.h"
#include "core/os/os.h"
#include "core/string/print_string.h"

#define QOI_NO_STDIO
#include "thirdparty/misc/qoi.h"

#include <string.h>

Error qoi_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) {
qoi_desc desc;
void *pixels = qoi_decode(p_buffer, p_buffer_len, &desc, 0);

if (!pixels) {
return ERR_FILE_CORRUPT;
}

Vector<uint8_t> data;
Image::Format fmt;
if (desc.channels == 1) {
fmt = Image::FORMAT_L8;
data.resize(desc.height * desc.width);
} else if (desc.channels == 3) {
fmt = Image::FORMAT_RGB8;
data.resize(desc.height * desc.width * 3);
} else {
fmt = Image::FORMAT_RGBA8;
data.resize(desc.height * desc.width * 4);
}

memcpy(data.ptrw(), pixels, data.size());
memfree(pixels);

p_image->set_data(desc.width, desc.height, false, fmt, data);

return OK;
}

Error ImageLoaderQOI::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
Vector<uint8_t> src_image;
uint64_t src_image_len = f->get_length();
ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT);
src_image.resize(src_image_len);

uint8_t *w = src_image.ptrw();

f->get_buffer(&w[0], src_image_len);

Error err = qoi_load_image_from_buffer(p_image.ptr(), w, src_image_len);

return err;
}

void ImageLoaderQOI::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("qoi");
}

static Ref<Image> _qoi_mem_loader_func(const uint8_t *p_bmp, int p_size) {
Ref<FileAccessMemory> memfile;
memfile.instantiate();
Error open_memfile_error = memfile->open_custom(p_bmp, p_size);
ERR_FAIL_COND_V_MSG(open_memfile_error, Ref<Image>(), "Could not create memfile for QOI image buffer.");

Ref<Image> img;
img.instantiate();
Error load_error = ImageLoaderQOI().load_image(img, memfile, false, 1.0f);
ERR_FAIL_COND_V_MSG(load_error, Ref<Image>(), "Failed to load QOI image.");
return img;
}

ImageLoaderQOI::ImageLoaderQOI() {
Image::_qoi_mem_loader_func = _qoi_mem_loader_func;
}
43 changes: 43 additions & 0 deletions modules/qoi/image_loader_qoi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**************************************************************************/
/* image_loader_qoi.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/

#ifndef IMAGE_LOADER_QOI_H
#define IMAGE_LOADER_QOI_H

#include "core/io/image_loader.h"

class ImageLoaderQOI : public ImageFormatLoader {
public:
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
ImageLoaderQOI();
};

#endif // IMAGE_LOADER_QOI_H
39 changes: 39 additions & 0 deletions modules/qoi/qoi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**************************************************************************/
/* qoi.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/

#include "core/os/memory.h"

#define QOI_NO_STDIO
#define QOI_IMPLEMENTATION

#define QOI_MALLOC(m_size) Memory::alloc_static(m_size)
#define QOI_FREE(p) Memory::free_static(p)

#include "thirdparty/misc/qoi.h"
Loading

0 comments on commit 6fe9e86

Please sign in to comment.