diff --git a/core/io/image.cpp b/core/io/image.cpp index 3ca39f98c052..733741fb2f2e 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -3016,6 +3016,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::_dds_mem_loader_func = nullptr; ImageMemLoadFunc Image::_ktx_mem_loader_func = nullptr; @@ -3490,6 +3491,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_dds_from_buffer", "buffer"), &Image::load_dds_from_buffer); ClassDB::bind_method(D_METHOD("load_ktx_from_buffer", "buffer"), &Image::load_ktx_from_buffer); @@ -3845,6 +3847,14 @@ Error Image::load_bmp_from_buffer(const Vector &p_array) { return _load_from_buffer(p_array, _bmp_mem_loader_func); } +Error Image::load_qoi_from_buffer(const Vector &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 &p_array, float scale) { ERR_FAIL_NULL_V_MSG( _svg_scalable_mem_loader_func, diff --git a/core/io/image.h b/core/io/image.h index cb7c6bff5296..155794a744fa 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -149,6 +149,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 _dds_mem_loader_func; static ImageMemLoadFunc _ktx_mem_loader_func; @@ -404,6 +405,7 @@ class Image : public Resource { Error load_webp_from_buffer(const Vector &p_array); Error load_tga_from_buffer(const Vector &p_array); Error load_bmp_from_buffer(const Vector &p_array); + Error load_qoi_from_buffer(const Vector &p_array); Error load_dds_from_buffer(const Vector &p_array); Error load_ktx_from_buffer(const Vector &p_array); diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 6451062fc552..149f26ed6405 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -347,6 +347,13 @@ Loads an image from the binary contents of a PNG file. + + + + + Loads an image from the binary contents of a QOI file. + + diff --git a/modules/qoi/SCsub b/modules/qoi/SCsub new file mode 100644 index 000000000000..aad38ff31a4d --- /dev/null +++ b/modules/qoi/SCsub @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_qoi = env_modules.Clone() + +# Godot source files +env_qoi.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/qoi/config.py b/modules/qoi/config.py new file mode 100644 index 000000000000..d22f9454ed25 --- /dev/null +++ b/modules/qoi/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return True + + +def configure(env): + pass diff --git a/modules/qoi/image_loader_qoi.cpp b/modules/qoi/image_loader_qoi.cpp new file mode 100644 index 000000000000..37e017e33b78 --- /dev/null +++ b/modules/qoi/image_loader_qoi.cpp @@ -0,0 +1,149 @@ +/**************************************************************************/ +/* 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 + +#include "core/io/file_access_memory.h" + +Error ImageLoaderqoi::load_image(Ref p_image, Ref f, BitField p_flags, float p_scale) { + Error err = ERR_INVALID_DATA; + Vector data; + f->big_endian = true; + + //qoi data + qoi_header_s qoi_header; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, px_pos; + int run = 0; + int write_px_pos = 0; + + qoi_header.magic = f->get_32(); + + //Every qoi file must start with "qoif". + if (qoi_header.magic != QOI_MAGIC) { + ERR_FAIL_COND_V_MSG(qoi_header.magic != QOI_MAGIC, ERR_BUG, "QOI images must start with 'qoif'."); + return err; + } + + qoi_header.width = f->get_32(); + qoi_header.height = f->get_32(); + qoi_header.channels = f->get_8(); + qoi_header.colorspace = f->get_8(); + + if (qoi_header.width == 0 || qoi_header.height == 0 || qoi_header.channels < 3 || + qoi_header.channels > 4 || qoi_header.colorspace > 1 || + qoi_header.height >= QOI_PIXELS_MAX / qoi_header.width) { + ERR_FAIL_COND_V_MSG(qoi_header.width == 0 || qoi_header.height == 0 || qoi_header.channels < 3 || + qoi_header.channels > 4 || qoi_header.colorspace > 1 || + qoi_header.height >= QOI_PIXELS_MAX / qoi_header.width, + ERR_BUG, "Couldn't parse the qoi image data."); + return err; + } + + px_len = qoi_header.width * qoi_header.height * qoi_header.channels; + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + data.resize(qoi_header.width * qoi_header.height * 4); + uint8_t *data_w = data.ptrw(); + + for (px_pos = 0; px_pos < px_len; px_pos += qoi_header.channels) { + write_px_pos += 4; + if (run > 0) { + run--; + } else { + int b1 = f->get_8(); + if (b1 == QOI_OP_RGB) { + px.rgba.r = f->get_8(); + px.rgba.g = f->get_8(); + px.rgba.b = f->get_8(); + } else if (b1 == QOI_OP_RGBA) { + px.rgba.r = f->get_8(); + px.rgba.g = f->get_8(); + px.rgba.b = f->get_8(); + px.rgba.a = f->get_8(); + } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += (b1 & 0x03) - 2; + } else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = f->get_8(); + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + index[QOI_COLOR_HASH(px) % 64] = px; + } + + data_w[write_px_pos + 0] = px.rgba.r; + data_w[write_px_pos + 1] = px.rgba.g; + data_w[write_px_pos + 2] = px.rgba.b; + + if (qoi_header.channels == 4) { + data_w[write_px_pos + 3] = px.rgba.a; + } else { + data_w[write_px_pos + 3] = 255; + } + } + p_image->set_data(qoi_header.width, qoi_header.height, false, Image::FORMAT_RGBA8, data); + err = OK; + return err; +} + +void ImageLoaderqoi::get_recognized_extensions(List *p_extensions) const { + p_extensions->push_back("qoi"); +} + +static Ref _qoi_mem_loader_func(const uint8_t *p_qoi, int p_size) { + Ref memfile; + memfile.instantiate(); + Error open_memfile_error = memfile->open_custom(p_qoi, p_size); + ERR_FAIL_COND_V_MSG(open_memfile_error, Ref(), "Could not create memfile for qoi image buffer."); + + Ref img; + img.instantiate(); + Error load_error = ImageLoaderqoi().load_image(img, memfile, false, 1.0f); + ERR_FAIL_COND_V_MSG(load_error, Ref(), "Failed to load qoi image."); + return img; +} + +ImageLoaderqoi::ImageLoaderqoi() { + Image::_qoi_mem_loader_func = _qoi_mem_loader_func; +} diff --git a/modules/qoi/image_loader_qoi.h b/modules/qoi/image_loader_qoi.h new file mode 100644 index 000000000000..89ee5218b53d --- /dev/null +++ b/modules/qoi/image_loader_qoi.h @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* 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 { +protected: + static const uint32_t QOI_MAGIC = 0x716f6966; // qoif in hex + + static const unsigned int QOI_PIXELS_MAX = 400000000; + + struct qoi_header_s { + uint32_t magic; + uint32_t width; + uint32_t height; + uint8_t channels; + uint8_t colorspace; + }; + + typedef union { + struct { + unsigned char r, g, b, a; + } rgba; + unsigned int v; + } qoi_rgba_t; + + static const uint8_t QOI_OP_INDEX = 0x00; /* 00xxxxxx */ + static const uint8_t QOI_OP_DIFF = 0x40; /* 01xxxxxx */ + static const uint8_t QOI_OP_LUMA = 0x80; /* 10xxxxxx */ + static const uint8_t QOI_OP_RUN = 0xc0; /* 11xxxxxx */ + static const uint8_t QOI_OP_RGB = 0xfe; /* 11111110 */ + static const uint8_t QOI_OP_RGBA = 0xff; /* 11111111 */ + static const uint8_t QOI_MASK_2 = 0xc0; /* 11000000 */ + + int QOI_COLOR_HASH(qoi_rgba_t c) { + return c.rgba.r * 3 + c.rgba.g * 5 + c.rgba.b * 7 + c.rgba.a * 11; + } + +public: + virtual Error load_image(Ref p_image, Ref f, BitField p_flags, float p_scale); + virtual void get_recognized_extensions(List *p_extensions) const; + ImageLoaderqoi(); +}; + +#endif // IMAGE_LOADER_QOI_H diff --git a/modules/qoi/register_types.cpp b/modules/qoi/register_types.cpp new file mode 100644 index 000000000000..2ac42e6d3fd9 --- /dev/null +++ b/modules/qoi/register_types.cpp @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* register_types.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 "register_types.h" + +#include "image_loader_qoi.h" + +static Ref image_loader_qoi; + +void initialize_qoi_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + image_loader_qoi.instantiate(); + ImageLoader::add_image_format_loader(image_loader_qoi); +} + +void uninitialize_qoi_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + ImageLoader::remove_image_format_loader(image_loader_qoi); + image_loader_qoi.unref(); +} diff --git a/modules/qoi/register_types.h b/modules/qoi/register_types.h new file mode 100644 index 000000000000..b22a7db16136 --- /dev/null +++ b/modules/qoi/register_types.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* register_types.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 QOI_REGISTER_TYPES_H +#define QOI_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_qoi_module(ModuleInitializationLevel p_level); +void uninitialize_qoi_module(ModuleInitializationLevel p_level); + +#endif // QOI_REGISTER_TYPES_H