Squashed 'external/libqoirdo/' content from commit 59f81203c9

git-subtree-dir: external/libqoirdo
git-subtree-split: 59f81203c99b2bd6edda0c84b98ba66a38f0e2c4
This commit is contained in:
Green Sky 2025-05-12 20:44:00 +02:00
commit 1c189bfd9c
71 changed files with 3276 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

29
CMakeLists.txt Normal file
View File

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.10)
project(libqoirdo)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
message( ${PROJECT_NAME} " build type: " ${CMAKE_BUILD_TYPE} )
add_library(qoirdo
./qoirdo.hpp
./qoirdo.cpp
)
target_compile_features(qoirdo PUBLIC cxx_std_11)
#if (NOT MSVC)
# target_link_libraries(rdopng m pthread)
#endif()
########################################
add_executable(qoirdo_tool
tool.cpp
)
target_link_libraries(qoirdo_tool qoirdo)

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

120
README.md Normal file
View File

@ -0,0 +1,120 @@
# rdopng
Rate-Distortion Optimized Lossy PNG, QOI, and LZ4 image (LZ4I) Encoding Tool
rdopng is a command line tool which uses LZ match optimization, Lagrangian multiplier [rate distortion optimization (RDO)](https://en.wikipedia.org/wiki/Rate%E2%80%93distortion_optimization), a simple perceptual error tolerance model, and [Oklab](https://bottosson.github.io/posts/oklab/)-based colorspace error metrics to encode lossy 24/32bpp PNG/QOI/LZ4I files. The encoded lossy PNG files are typically 30-80% smaller relative to lodepng/libpng. The tool defaults to reasonably fast near-lossless settings which writes PNG's around 30-40% smaller than lossless PNG encoders.
Unlike [pngquant](https://pngquant.org/), rdopng does not use 256-color palettes or dithering. PNG files encoded by rdopng typically range between roughly 2.5-7bpp, depending on the options used (and how much time and patience you have).
Some example encodes and command lines are [here](https://github.com/richgel999/rdopng/wiki/Examples).
You can download a pre-built Windows binary for an older version of rdopng [here](https://github.com/richgel999/rdopng/releases). (The latest version is in the repo.) You may need to install the [VS 2022 runtime redistributable from Microsoft](https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170).
### Building
You'll need [cmake](https://cmake.org/). There are no other dependencies.
Linux (gcc/clang):
```
cmake .
make
```
Windows (tested with Visual Studio 2022):
```
cmake .
rdopng.sln
```
### Instructions
Encodes a .PNG/.BMP/.TGA/.JPG file to "./file_rdo.png":
```
rdopng file.png
```
Encodes a .PNG/.BMP/.TGA/.JPG file to "./file_rdo.qoi" (and also unpacks the coded image and saves it as .PNG):
```
rdopng -qoi -unpack_qoi_to_png file.png
```
Encodes a file to "./file_rdo.qoi" at higher quality per bit, but much slower (also try -better which is in between the default/uber settings):
```
rdopng -qoi -uber -unpack_qoi_to_png file.png
```
Encodes smaller PNG files but will be 2x slower:
```
rdopng -two_pass file.png
```
Encodes at lower than default quality (which is 300), but writes smaller files:
```
rdopng -lambda 500 file.png
```
Significantly lower PNG quality (which increases artifacts), using a higher than default parsing level to compensate for artifacts:
```
rdopng -level 3 -lambda 1000 file.png
```
Enable debug output and write output to z.png:
```
rdopng -debug file.png -output z.png
```
Load a normal map, normalize it, pack it using angular normal map metrics, decoded/encode texels using GPU SNORM unpacking (instead of the default UNORM):
```
rdopng -normalize -normal_map -snorm file.png
```
Level ranges from 0-29. Levels 0-9 use up to 4 pixel long matches, levels 10-17 use up to 6 pixel long matches, and 18-23 use up to 6 or 12 pixel long matches. Levels 24-29 use exhaustive matching and are beyond impractical except on tiny images.
The higher the level within a match length category, the slower the encoder. Higher match length categories are needed for the higher lambdas/lower bitrates. At near-lossless settings (lower than approximately lambda 300), the smaller/less aggressive parsing levels are usually fine. At higher lambdas/lower bitrates the higher levels are needed to avoid artifacts. To get below roughly 3-4bpp you'll need to use high lambdas, two pass mode, and very slow parsing levels.
-lambda is the quality slider. Useful lambda values are roughly 1-20000, but values beyond approximately 500-1000 (depending on the image) will require fiddling with the level to compensate for artifacts. Higher levels are extremely slow because the current tool is single threaded.
Most options work with both QOI, LZ4I and PNG. The -level option is only for PNG, and the -uber/-better options are only for QOI/LZ4I.
### RDO LZ4 examples
```
rdopng -lz4i -lambda 5000 -debug -better file.png
```
Unpacking .LZ4I images to PNG:
```
rdopng -unpack file.lz4i
```
LZ4I image files contain a simple header followed by the RGB(A) pixels compressed using LZ4. Here's the header (it's like QOI's but with a different sig):
```
#pragma pack(push, 1)
struct lz4i_header
{
char sig[4]; // signature bytes "lz4i"
uint32_t width; // image width in pixels (BE)
uint32_t height; // image height in pixels (BE)
uint8_t channels; // 3 = RGB, 4 = RGBA
uint8_t colorspace; // 0 = sRGB with linear alpha 1 = all channels linear
};
#pragma pack(pop)
```
### Known Problems
rdopng has only been tested on little endian platforms, under Windows using MSVC and Ubuntu Linux using clang/gcc. There are a few known endian issues in there, which I'll eventually fix. It has not been compiled or tested on OSX.
### Special Thanks
Thanks to [Paul Hughes](https://twitter.com/PaulieHughes) for encouraging me to continue working on this on Twitter. Also, thanks to [Jyrki Alakuijala](https://twitter.com/jyzg) for suggesting to drop YCbCr for an alternative such as Oklab.

867
basisu.min.hpp Normal file
View File

@ -0,0 +1,867 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <cassert>
#include <cstring>
#include <vector>
namespace basisu
{
using std::clamp;
using std::min;
using std::max;
template <typename T0, typename T1> inline T0 lerp(T0 a, T0 b, T1 c) { return a + (b - a) * c; }
class color_rgba
{
public:
union
{
uint8_t m_comps[4];
struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
};
inline color_rgba()
{
static_assert(sizeof(*this) == 4, "sizeof(*this) != 4");
}
inline color_rgba(int y)
{
set(y);
}
inline color_rgba(int y, int na)
{
set(y, na);
}
inline color_rgba(int sr, int sg, int sb, int sa)
{
set(sr, sg, sb, sa);
}
//inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa)
//{
// set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa);
//}
inline color_rgba& set_noclamp_y(int y)
{
m_comps[0] = (uint8_t)y;
m_comps[1] = (uint8_t)y;
m_comps[2] = (uint8_t)y;
m_comps[3] = (uint8_t)255;
return *this;
}
inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa)
{
m_comps[0] = (uint8_t)sr;
m_comps[1] = (uint8_t)sg;
m_comps[2] = (uint8_t)sb;
m_comps[3] = (uint8_t)sa;
return *this;
}
inline color_rgba &set(int y)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
m_comps[1] = m_comps[0];
m_comps[2] = m_comps[0];
m_comps[3] = 255;
return *this;
}
inline color_rgba &set(int y, int na)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
m_comps[1] = m_comps[0];
m_comps[2] = m_comps[0];
m_comps[3] = static_cast<uint8_t>(clamp<int>(na, 0, 255));
return *this;
}
inline color_rgba &set(int sr, int sg, int sb, int sa)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
m_comps[3] = static_cast<uint8_t>(clamp<int>(sa, 0, 255));
return *this;
}
inline color_rgba &set_rgb(int sr, int sg, int sb)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
return *this;
}
inline color_rgba &set_rgb(const color_rgba &other)
{
r = other.r;
g = other.g;
b = other.b;
return *this;
}
inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; }
inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; }
inline void clear()
{
m_comps[0] = 0;
m_comps[1] = 0;
m_comps[2] = 0;
m_comps[3] = 0;
}
inline bool operator== (const color_rgba &rhs) const
{
if (m_comps[0] != rhs.m_comps[0]) return false;
if (m_comps[1] != rhs.m_comps[1]) return false;
if (m_comps[2] != rhs.m_comps[2]) return false;
if (m_comps[3] != rhs.m_comps[3]) return false;
return true;
}
inline bool operator!= (const color_rgba &rhs) const
{
return !(*this == rhs);
}
inline bool operator<(const color_rgba &rhs) const
{
for (int i = 0; i < 4; i++)
{
if (m_comps[i] < rhs.m_comps[i])
return true;
else if (m_comps[i] != rhs.m_comps[i])
return false;
}
return false;
}
inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; }
inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; }
inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); }
static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(min(a[0], b[0]), min(a[1], b[1]), min(a[2], b[2]), min(a[3], b[3])); }
static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(max(a[0], b[0]), max(a[1], b[1]), max(a[2], b[2]), max(a[3], b[3])); }
};
typedef std::vector<color_rgba> color_rgba_vec;
const color_rgba g_black_color(0, 0, 0, 255);
const color_rgba g_black_trans_color(0, 0, 0, 0);
const color_rgba g_white_color(255, 255, 255, 255);
// Simple 32-bit 2D image class
class image
{
public:
image() :
m_width(0), m_height(0), m_pitch(0)
{
}
image(uint32_t w, uint32_t h, uint32_t p = UINT32_MAX) :
m_width(0), m_height(0), m_pitch(0)
{
resize(w, h, p);
}
image(const uint8_t *pImage, uint32_t width, uint32_t height, uint32_t comps) :
m_width(0), m_height(0), m_pitch(0)
{
init(pImage, width, height, comps);
}
image(const image &other) :
m_width(0), m_height(0), m_pitch(0)
{
*this = other;
}
image &swap(image &other)
{
std::swap(m_width, other.m_width);
std::swap(m_height, other.m_height);
std::swap(m_pitch, other.m_pitch);
m_pixels.swap(other.m_pixels);
return *this;
}
image &operator= (const image &rhs)
{
if (this != &rhs)
{
m_width = rhs.m_width;
m_height = rhs.m_height;
m_pitch = rhs.m_pitch;
m_pixels = rhs.m_pixels;
}
return *this;
}
image &clear()
{
m_width = 0;
m_height = 0;
m_pitch = 0;
m_pixels.erase(m_pixels.begin(), m_pixels.end());
return *this;
}
image &resize(uint32_t w, uint32_t h, uint32_t p = UINT32_MAX, const color_rgba& background = g_black_color)
{
return crop(w, h, p, background);
}
image &set_all(const color_rgba &c)
{
for (uint32_t i = 0; i < m_pixels.size(); i++)
m_pixels[i] = c;
return *this;
}
void init(const uint8_t *pImage, uint32_t width, uint32_t height, uint32_t comps)
{
assert(comps >= 1 && comps <= 4);
resize(width, height);
for (uint32_t y = 0; y < height; y++)
{
for (uint32_t x = 0; x < width; x++)
{
const uint8_t *pSrc = &pImage[(x + y * width) * comps];
color_rgba &dst = (*this)(x, y);
if (comps == 1)
{
dst.r = pSrc[0];
dst.g = pSrc[0];
dst.b = pSrc[0];
dst.a = 255;
}
else if (comps == 2)
{
dst.r = pSrc[0];
dst.g = pSrc[0];
dst.b = pSrc[0];
dst.a = pSrc[1];
}
else
{
dst.r = pSrc[0];
dst.g = pSrc[1];
dst.b = pSrc[2];
if (comps == 4)
dst.a = pSrc[3];
else
dst.a = 255;
}
}
}
}
image &fill_box(uint32_t x, uint32_t y, uint32_t w, uint32_t h, const color_rgba &c)
{
for (uint32_t iy = 0; iy < h; iy++)
for (uint32_t ix = 0; ix < w; ix++)
set_clipped(x + ix, y + iy, c);
return *this;
}
image& fill_box_alpha(uint32_t x, uint32_t y, uint32_t w, uint32_t h, const color_rgba& c)
{
for (uint32_t iy = 0; iy < h; iy++)
for (uint32_t ix = 0; ix < w; ix++)
set_clipped_alpha(x + ix, y + iy, c);
return *this;
}
image &crop_dup_borders(uint32_t w, uint32_t h)
{
const uint32_t orig_w = m_width, orig_h = m_height;
crop(w, h);
if (orig_w && orig_h)
{
if (m_width > orig_w)
{
for (uint32_t x = orig_w; x < m_width; x++)
for (uint32_t y = 0; y < m_height; y++)
set_clipped(x, y, get_clamped(min(x, orig_w - 1U), min(y, orig_h - 1U)));
}
if (m_height > orig_h)
{
for (uint32_t y = orig_h; y < m_height; y++)
for (uint32_t x = 0; x < m_width; x++)
set_clipped(x, y, get_clamped(min(x, orig_w - 1U), min(y, orig_h - 1U)));
}
}
return *this;
}
//// pPixels MUST have been allocated using malloc() (basisu::vector will eventually use free() on the pointer).
//image& grant_ownership(color_rgba* pPixels, uint32_t w, uint32_t h, uint32_t p = UINT32_MAX)
//{
// if (p == UINT32_MAX)
// p = w;
// clear();
// if ((!p) || (!w) || (!h))
// return *this;
// m_pixels.grant_ownership(pPixels, p * h, p * h);
// m_width = w;
// m_height = h;
// m_pitch = p;
// return *this;
//}
image &crop(uint32_t w, uint32_t h, uint32_t p = UINT32_MAX, const color_rgba &background = g_black_color, bool init_image = true)
{
if (p == UINT32_MAX)
p = w;
if ((w == m_width) && (m_height == h) && (m_pitch == p))
return *this;
if ((!w) || (!h) || (!p))
{
clear();
return *this;
}
color_rgba_vec cur_state;
cur_state.swap(m_pixels);
m_pixels.resize(p * h);
if (init_image)
{
if (m_width || m_height)
{
for (uint32_t y = 0; y < h; y++)
{
for (uint32_t x = 0; x < w; x++)
{
if ((x < m_width) && (y < m_height))
m_pixels[x + y * p] = cur_state[x + y * m_pitch];
else
m_pixels[x + y * p] = background;
}
}
}
else
{
//m_pixels.set_all(background);
set_all(background);
}
}
m_width = w;
m_height = h;
m_pitch = p;
return *this;
}
inline const color_rgba &operator() (uint32_t x, uint32_t y) const { assert(x < m_width && y < m_height); return m_pixels[x + y * m_pitch]; }
inline color_rgba &operator() (uint32_t x, uint32_t y) { assert(x < m_width && y < m_height); return m_pixels[x + y * m_pitch]; }
inline const color_rgba& get_pixel(uint32_t c) const { return (*this)(c % m_width, c / m_width); }
inline color_rgba& get_pixel(uint32_t c) { return (*this)(c % m_width, c / m_width); }
inline const color_rgba &get_clamped(int x, int y) const { return (*this)(clamp<int>(x, 0, m_width - 1), clamp<int>(y, 0, m_height - 1)); }
inline color_rgba &get_clamped(int x, int y) { return (*this)(clamp<int>(x, 0, m_width - 1), clamp<int>(y, 0, m_height - 1)); }
//inline const color_rgba &get_clamped_or_wrapped(int x, int y, bool wrap_u, bool wrap_v) const
//{
// x = wrap_u ? posmod(x, m_width) : clamp<int>(x, 0, m_width - 1);
// y = wrap_v ? posmod(y, m_height) : clamp<int>(y, 0, m_height - 1);
// return m_pixels[x + y * m_pitch];
//}
//inline color_rgba &get_clamped_or_wrapped(int x, int y, bool wrap_u, bool wrap_v)
//{
// x = wrap_u ? posmod(x, m_width) : clamp<int>(x, 0, m_width - 1);
// y = wrap_v ? posmod(y, m_height) : clamp<int>(y, 0, m_height - 1);
// return m_pixels[x + y * m_pitch];
//}
inline image &set_clipped(int x, int y, const color_rgba &c)
{
if ((static_cast<uint32_t>(x) < m_width) && (static_cast<uint32_t>(y) < m_height))
(*this)(x, y) = c;
return *this;
}
inline image& set_clipped_alpha(int x, int y, const color_rgba& c)
{
if ((static_cast<uint32_t>(x) < m_width) && (static_cast<uint32_t>(y) < m_height))
(*this)(x, y).m_comps[3] = c.m_comps[3];
return *this;
}
// Very straightforward blit with full clipping. Not fast, but it works.
image &blit(const image &src, int src_x, int src_y, int src_w, int src_h, int dst_x, int dst_y)
{
for (int y = 0; y < src_h; y++)
{
const int sy = src_y + y;
if (sy < 0)
continue;
else if (sy >= (int)src.get_height())
break;
for (int x = 0; x < src_w; x++)
{
const int sx = src_x + x;
if (sx < 0)
continue;
else if (sx >= (int)src.get_height())
break;
set_clipped(dst_x + x, dst_y + y, src(sx, sy));
}
}
return *this;
}
const image &extract_block_clamped(color_rgba *pDst, uint32_t src_x, uint32_t src_y, uint32_t w, uint32_t h) const
{
if (((src_x + w) > m_width) || ((src_y + h) > m_height))
{
// Slower clamping case
for (uint32_t y = 0; y < h; y++)
for (uint32_t x = 0; x < w; x++)
*pDst++ = get_clamped(src_x + x, src_y + y);
}
else
{
const color_rgba* pSrc = &m_pixels[src_x + src_y * m_pitch];
for (uint32_t y = 0; y < h; y++)
{
std::memcpy(pDst, pSrc, w * sizeof(color_rgba));
pSrc += m_pitch;
pDst += w;
}
}
return *this;
}
image &set_block_clipped(const color_rgba *pSrc, uint32_t dst_x, uint32_t dst_y, uint32_t w, uint32_t h)
{
for (uint32_t y = 0; y < h; y++)
for (uint32_t x = 0; x < w; x++)
set_clipped(dst_x + x, dst_y + y, *pSrc++);
return *this;
}
inline uint32_t get_width() const { return m_width; }
inline uint32_t get_height() const { return m_height; }
inline uint32_t get_pitch() const { return m_pitch; }
inline uint32_t get_total_pixels() const { return m_width * m_height; }
inline uint32_t get_block_width(uint32_t w) const { return (m_width + (w - 1)) / w; }
inline uint32_t get_block_height(uint32_t h) const { return (m_height + (h - 1)) / h; }
inline uint32_t get_total_blocks(uint32_t w, uint32_t h) const { return get_block_width(w) * get_block_height(h); }
inline const color_rgba_vec &get_pixels() const { return m_pixels; }
inline color_rgba_vec &get_pixels() { return m_pixels; }
inline const color_rgba *get_ptr() const { return &m_pixels[0]; }
inline color_rgba *get_ptr() { return &m_pixels[0]; }
bool has_alpha() const
{
for (uint32_t y = 0; y < m_height; ++y)
for (uint32_t x = 0; x < m_width; ++x)
if ((*this)(x, y).a < 255)
return true;
return false;
}
image &set_alpha(uint8_t a)
{
for (uint32_t y = 0; y < m_height; ++y)
for (uint32_t x = 0; x < m_width; ++x)
(*this)(x, y).a = a;
return *this;
}
image &flip_y()
{
for (uint32_t y = 0; y < m_height / 2; ++y)
for (uint32_t x = 0; x < m_width; ++x)
std::swap((*this)(x, y), (*this)(x, m_height - 1 - y));
return *this;
}
//// TODO: There are many ways to do this, not sure this is the best way.
//image &renormalize_normal_map()
//{
// for (uint32_t y = 0; y < m_height; y++)
// {
// for (uint32_t x = 0; x < m_width; x++)
// {
// color_rgba &c = (*this)(x, y);
// if ((c.r == 128) && (c.g == 128) && (c.b == 128))
// continue;
// vec3F v(c.r, c.g, c.b);
// v = (v * (2.0f / 255.0f)) - vec3F(1.0f);
// v.clamp(-1.0f, 1.0f);
// float length = v.length();
// const float cValidThresh = .077f;
// if (length < cValidThresh)
// {
// c.set(128, 128, 128, c.a);
// }
// else if (fabs(length - 1.0f) > cValidThresh)
// {
// if (length)
// v /= length;
// for (uint32_t i = 0; i < 3; i++)
// c[i] = static_cast<uint8_t>(clamp<float>(floor((v[i] + 1.0f) * 255.0f * .5f + .5f), 0.0f, 255.0f));
// if ((c.g == 128) && (c.r == 128))
// {
// if (c.b < 128)
// c.b = 0;
// else
// c.b = 255;
// }
// }
// }
// }
// return *this;
//}
bool operator== (const image& img) const
{
if ((m_width != img.get_width()) || (m_height != img.get_height()))
return false;
for (uint32_t y = 0; y < m_height; y++)
for (uint32_t x = 0; x < m_width; x++)
if ((*this)(x, y) != img(x, y))
return false;
return true;
}
bool operator!= (const image& img) const
{
return !(*this == img);
}
void debug_text(uint32_t x_ofs, uint32_t y_ofs, uint32_t x_scale, uint32_t y_scale, const color_rgba &fg, const color_rgba *pBG, bool alpha_only, const char* p, ...);
private:
uint32_t m_width, m_height, m_pitch; // all in pixels
color_rgba_vec m_pixels;
};
enum eZero { cZero };
// Linear algebra
template <uint32_t N, typename T>
class vec
{
protected:
T m_v[N];
public:
enum { num_elements = N };
inline vec() { }
inline vec(eZero) { set_zero(); }
explicit inline vec(T val) { set(val); }
inline vec(T v0, T v1) { set(v0, v1); }
inline vec(T v0, T v1, T v2) { set(v0, v1, v2); }
inline vec(T v0, T v1, T v2, T v3) { set(v0, v1, v2, v3); }
inline vec(const vec &other) { for (uint32_t i = 0; i < N; i++) m_v[i] = other.m_v[i]; }
template <uint32_t OtherN, typename OtherT> inline vec(const vec<OtherN, OtherT> &other) { set(other); }
inline T operator[](uint32_t i) const { assert(i < N); return m_v[i]; }
inline T &operator[](uint32_t i) { assert(i < N); return m_v[i]; }
inline T getX() const { return m_v[0]; }
inline T getY() const { static_assert(N >= 2, "N too small"); return m_v[1]; }
inline T getZ() const { static_assert(N >= 3, "N too small"); return m_v[2]; }
inline T getW() const { static_assert(N >= 4, "N too small"); return m_v[3]; }
inline bool operator==(const vec &rhs) const { for (uint32_t i = 0; i < N; i++) if (m_v[i] != rhs.m_v[i]) return false; return true; }
inline bool operator<(const vec &rhs) const { for (uint32_t i = 0; i < N; i++) { if (m_v[i] < rhs.m_v[i]) return true; else if (m_v[i] != rhs.m_v[i]) return false; } return false; }
inline void set_zero() { for (uint32_t i = 0; i < N; i++) m_v[i] = 0; }
template <uint32_t OtherN, typename OtherT>
inline vec &set(const vec<OtherN, OtherT> &other)
{
uint32_t i;
if ((const void *)(&other) == (const void *)(this))
return *this;
const uint32_t m = min(OtherN, N);
for (i = 0; i < m; i++)
m_v[i] = static_cast<T>(other[i]);
for (; i < N; i++)
m_v[i] = 0;
return *this;
}
inline vec &set_component(uint32_t index, T val) { assert(index < N); m_v[index] = val; return *this; }
inline vec &set(T val) { for (uint32_t i = 0; i < N; i++) m_v[i] = val; return *this; }
inline void clear_elements(uint32_t s, uint32_t e) { assert(e <= N); for (uint32_t i = s; i < e; i++) m_v[i] = 0; }
inline vec &set(T v0, T v1)
{
m_v[0] = v0;
if (N >= 2)
{
m_v[1] = v1;
clear_elements(2, N);
}
return *this;
}
inline vec &set(T v0, T v1, T v2)
{
m_v[0] = v0;
if (N >= 2)
{
m_v[1] = v1;
if (N >= 3)
{
m_v[2] = v2;
clear_elements(3, N);
}
}
return *this;
}
inline vec &set(T v0, T v1, T v2, T v3)
{
m_v[0] = v0;
if (N >= 2)
{
m_v[1] = v1;
if (N >= 3)
{
m_v[2] = v2;
if (N >= 4)
{
m_v[3] = v3;
clear_elements(5, N);
}
}
}
return *this;
}
inline vec &operator=(const vec &rhs) { if (this != &rhs) for (uint32_t i = 0; i < N; i++) m_v[i] = rhs.m_v[i]; return *this; }
template <uint32_t OtherN, typename OtherT> inline vec &operator=(const vec<OtherN, OtherT> &rhs) { set(rhs); return *this; }
inline const T *get_ptr() const { return reinterpret_cast<const T *>(&m_v[0]); }
inline T *get_ptr() { return reinterpret_cast<T *>(&m_v[0]); }
inline vec operator- () const { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = -m_v[i]; return res; }
inline vec operator+ () const { return *this; }
inline vec &operator+= (const vec &other) { for (uint32_t i = 0; i < N; i++) m_v[i] += other.m_v[i]; return *this; }
inline vec &operator-= (const vec &other) { for (uint32_t i = 0; i < N; i++) m_v[i] -= other.m_v[i]; return *this; }
inline vec &operator/= (const vec &other) { for (uint32_t i = 0; i < N; i++) m_v[i] /= other.m_v[i]; return *this; }
inline vec &operator*=(const vec &other) { for (uint32_t i = 0; i < N; i++) m_v[i] *= other.m_v[i]; return *this; }
inline vec &operator/= (T s) { for (uint32_t i = 0; i < N; i++) m_v[i] /= s; return *this; }
inline vec &operator*= (T s) { for (uint32_t i = 0; i < N; i++) m_v[i] *= s; return *this; }
friend inline vec operator+(const vec &lhs, const vec &rhs) { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = lhs.m_v[i] + rhs.m_v[i]; return res; }
friend inline vec operator-(const vec &lhs, const vec &rhs) { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = lhs.m_v[i] - rhs.m_v[i]; return res; }
friend inline vec operator*(const vec &lhs, T val) { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = lhs.m_v[i] * val; return res; }
friend inline vec operator*(T val, const vec &rhs) { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = val * rhs.m_v[i]; return res; }
friend inline vec operator/(const vec &lhs, T val) { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = lhs.m_v[i] / val; return res; }
friend inline vec operator/(const vec &lhs, const vec &rhs) { vec res; for (uint32_t i = 0; i < N; i++) res.m_v[i] = lhs.m_v[i] / rhs.m_v[i]; return res; }
static inline T dot_product(const vec &lhs, const vec &rhs) { T res = lhs.m_v[0] * rhs.m_v[0]; for (uint32_t i = 1; i < N; i++) res += lhs.m_v[i] * rhs.m_v[i]; return res; }
inline T dot(const vec &rhs) const { return dot_product(*this, rhs); }
inline T norm() const { return dot_product(*this, *this); }
inline T length() const { return sqrt(norm()); }
inline T squared_distance(const vec &other) const { T d2 = 0; for (uint32_t i = 0; i < N; i++) { T d = m_v[i] - other.m_v[i]; d2 += d * d; } return d2; }
inline double squared_distance_d(const vec& other) const { double d2 = 0; for (uint32_t i = 0; i < N; i++) { double d = (double)m_v[i] - (double)other.m_v[i]; d2 += d * d; } return d2; }
inline T distance(const vec &other) const { return static_cast<T>(sqrt(squared_distance(other))); }
inline double distance_d(const vec& other) const { return sqrt(squared_distance_d(other)); }
inline vec &normalize_in_place() { T len = length(); if (len != 0.0f) *this *= (1.0f / len); return *this; }
inline vec &clamp(T l, T h)
{
for (uint32_t i = 0; i < N; i++)
m_v[i] = basisu::clamp(m_v[i], l, h);
return *this;
}
static vec component_min(const vec& a, const vec& b)
{
vec res;
for (uint32_t i = 0; i < N; i++)
res[i] = min(a[i], b[i]);
return res;
}
static vec component_max(const vec& a, const vec& b)
{
vec res;
for (uint32_t i = 0; i < N; i++)
res[i] = max(a[i], b[i]);
return res;
}
};
typedef vec<4, double> vec4D;
typedef vec<3, double> vec3D;
typedef vec<2, double> vec2D;
typedef vec<1, double> vec1D;
typedef vec<4, float> vec4F;
typedef vec<3, float> vec3F;
typedef vec<2, float> vec2F;
typedef vec<1, float> vec1F;
typedef vec<16, float> vec16F;
// 2D array
template<typename T>
class vector2D
{
typedef std::vector<T> TVec;
uint32_t m_width, m_height;
TVec m_values;
public:
vector2D() :
m_width(0),
m_height(0)
{
}
vector2D(uint32_t w, uint32_t h) :
m_width(0),
m_height(0)
{
resize(w, h);
}
vector2D(const vector2D &other)
{
*this = other;
}
vector2D &operator= (const vector2D &other)
{
if (this != &other)
{
m_width = other.m_width;
m_height = other.m_height;
m_values = other.m_values;
}
return *this;
}
inline bool operator== (const vector2D &rhs) const
{
return (m_width == rhs.m_width) && (m_height == rhs.m_height) && (m_values == rhs.m_values);
}
inline uint32_t size_in_bytes() const { return (uint32_t)m_values.size() * sizeof(m_values[0]); }
inline const T &operator() (uint32_t x, uint32_t y) const { assert(x < m_width && y < m_height); return m_values[x + y * m_width]; }
inline T &operator() (uint32_t x, uint32_t y) { assert(x < m_width && y < m_height); return m_values[x + y * m_width]; }
inline const T &operator[] (uint32_t i) const { return m_values[i]; }
inline T &operator[] (uint32_t i) { return m_values[i]; }
inline const T &at_clamped(int x, int y) const { return (*this)(clamp<int>(x, 0, m_width), clamp<int>(y, 0, m_height)); }
inline T &at_clamped(int x, int y) { return (*this)(clamp<int>(x, 0, m_width), clamp<int>(y, 0, m_height)); }
void clear()
{
m_width = 0;
m_height = 0;
m_values.clear();
}
void set_all(const T&val)
{
//vector_set_all(m_values, val);
for (size_t i = 0; i < m_values.size(); i++)
m_values[i] = val;
}
inline const T* get_ptr() const { return &m_values[0]; }
inline T* get_ptr() { return &m_values[0]; }
vector2D &resize(uint32_t new_width, uint32_t new_height)
{
if ((m_width == new_width) && (m_height == new_height))
return *this;
TVec oldVals(new_width * new_height);
oldVals.swap(m_values);
const uint32_t w = min(m_width, new_width);
const uint32_t h = min(m_height, new_height);
if ((w) && (h))
{
for (uint32_t y = 0; y < h; y++)
for (uint32_t x = 0; x < w; x++)
m_values[x + y * new_width] = oldVals[x + y * m_width];
}
m_width = new_width;
m_height = new_height;
return *this;
}
};
} // basisu

BIN
examples/aliens.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

BIN
examples/aliens_2_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
examples/aliens_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

BIN
examples/crossyf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 KiB

BIN
examples/crossyf_2_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
examples/crossyf_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
examples/doom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

BIN
examples/doom_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

BIN
examples/doom_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

BIN
examples/gotham.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 KiB

BIN
examples/gotham_2_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

BIN
examples/gotham_2_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
examples/gotham_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

BIN
examples/gotham_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
examples/high_fidelity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
examples/joker_768.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

BIN
examples/joker_768_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
examples/kodim18.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

BIN
examples/kodim18_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

BIN
examples/kodim18_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
examples/lara_1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

BIN
examples/lara_1024_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
examples/magneto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
examples/magneto_2_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
examples/magneto_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
examples/magneto_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
examples/masterchief.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
examples/minerology.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

BIN
examples/minerology_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 KiB

BIN
examples/puppy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
examples/puppy_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
examples/puppy_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

BIN
examples/stp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 KiB

BIN
examples/stp_2_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 KiB

BIN
examples/stp_2_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

BIN
examples/stp_3_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

BIN
examples/stp_3_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

BIN
examples/stp_delta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

BIN
examples/stp_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

BIN
examples/waterfall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
examples/waterfall_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
examples/xfiles_768.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

BIN
examples/xfiles_768_rdo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

672
qoi.h Normal file
View File

@ -0,0 +1,672 @@
/*
QOI - The "Quite OK Image" format for fast, lossless image compression
Dominic Szablewski - https://phoboslab.org
-- LICENSE: The MIT License(MIT)
Copyright(c) 2021 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.
-- About
QOI encodes and decodes images in a lossless format. Compared to stb_image and
stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
20% better compression.
-- Synopsis
// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
// library to create the implementation.
#define QOI_IMPLEMENTATION
#include "qoi.h"
// Encode and store an RGBA buffer to the file system. The qoi_desc describes
// the input pixel data.
qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
.width = 1920,
.height = 1080,
.channels = 4,
.colorspace = QOI_SRGB
});
// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
// The qoi_desc struct will be filled with the width, height, number of channels
// and colorspace read from the file header.
qoi_desc desc;
void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
-- Documentation
This library provides the following functions;
- qoi_read -- read and decode a QOI file
- qoi_decode -- decode the raw bytes of a QOI image from memory
- qoi_write -- encode and write a QOI file
- qoi_encode -- encode an rgba buffer into a QOI image in memory
See the function declaration below for the signature and more information.
If you don't want/need the qoi_read and qoi_write functions, you can define
QOI_NO_STDIO before including this library.
This library uses malloc() and free(). To supply your own malloc implementation
you can define QOI_MALLOC and QOI_FREE before including this library.
This library uses memset() to zero-initialize the index. To supply your own
implementation you can define QOI_ZEROARR before including this library.
-- Data Format
A QOI file has a 14 byte header, followed by any number of data "chunks" and an
8-byte end marker.
struct qoi_header_t {
char magic[4]; // magic bytes "qoif"
uint32_t width; // image width in pixels (BE)
uint32_t height; // image height in pixels (BE)
uint8_t channels; // 3 = RGB, 4 = RGBA
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
};
Images are encoded row by row, left to right, top to bottom. The decoder and
encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
image is complete when all pixels specified by width * height have been covered.
Pixels are encoded as
- a run of the previous pixel
- an index into an array of previously seen pixels
- a difference to the previous pixel value in r,g,b
- full r,g,b or r,g,b,a values
The color channels are assumed to not be premultiplied with the alpha channel
("un-premultiplied alpha").
A running array[64] (zero-initialized) of previously seen pixel values is
maintained by the encoder and decoder. Each pixel that is seen by the encoder
and decoder is put into this array at the position formed by a hash function of
the color value. In the encoder, if the pixel value at the index matches the
current pixel, this index position is written to the stream as QOI_OP_INDEX.
The hash function for the index is:
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
values encoded in these data bits have the most significant bit on the left.
The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
presence of an 8-bit tag first.
The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
The possible chunks are:
.- QOI_OP_INDEX ----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 0 0 | index |
`-------------------------`
2-bit tag b00
6-bit index into the color index array: 0..63
A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
same index. QOI_OP_RUN should be used instead.
.- QOI_OP_DIFF -----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----+-----+-----|
| 0 1 | dr | dg | db |
`-------------------------`
2-bit tag b01
2-bit red channel difference from the previous pixel between -2..1
2-bit green channel difference from the previous pixel between -2..1
2-bit blue channel difference from the previous pixel between -2..1
The difference to the current channel values are using a wraparound operation,
so "1 - 2" will result in 255, while "255 + 1" will result in 0.
Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
0 (b00). 1 is stored as 3 (b11).
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_LUMA -------------------------------------.
| Byte[0] | Byte[1] |
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
|-------+-----------------+-------------+-----------|
| 1 0 | green diff | dr - dg | db - dg |
`---------------------------------------------------`
2-bit tag b10
6-bit green channel difference from the previous pixel -32..31
4-bit red channel difference minus green channel difference -8..7
4-bit blue channel difference minus green channel difference -8..7
The green channel is used to indicate the general direction of change and is
encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
of the green channel difference and are encoded in 4 bits. I.e.:
dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
The difference to the current channel values are using a wraparound operation,
so "10 - 13" will result in 253, while "250 + 7" will result in 1.
Values are stored as unsigned integers with a bias of 32 for the green channel
and a bias of 8 for the red and blue channel.
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_RUN ------------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 1 1 | run |
`-------------------------`
2-bit tag b11
6-bit run-length repeating the previous pixel: 1..62
The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
QOI_OP_RGBA tags.
.- QOI_OP_RGB ------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------|
| 1 1 1 1 1 1 1 0 | red | green | blue |
`-------------------------------------------------------`
8-bit tag b11111110
8-bit red channel value
8-bit green channel value
8-bit blue channel value
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_RGBA ---------------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------+---------|
| 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
`-----------------------------------------------------------------`
8-bit tag b11111111
8-bit red channel value
8-bit green channel value
8-bit blue channel value
8-bit alpha channel value
*/
/* -----------------------------------------------------------------------------
Header - Public functions */
#ifndef QOI_H
#define QOI_H
#ifdef __cplusplus
extern "C" {
#endif
/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
It describes either the input format (for qoi_write and qoi_encode), or is
filled with the description read from the file header (for qoi_read and
qoi_decode).
The colorspace in this qoi_desc is an enum where
0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
1 = all channels are linear
You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
informative. It will be saved to the file header, but does not affect
how chunks are en-/decoded. */
#define QOI_SRGB 0
#define QOI_LINEAR 1
typedef struct {
unsigned int width;
unsigned int height;
unsigned char channels;
unsigned char colorspace;
} qoi_desc;
#ifndef QOI_NO_STDIO
/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
system. The qoi_desc struct must be filled with the image width, height,
number of channels (3 = RGB, 4 = RGBA) and the colorspace.
The function returns 0 on failure (invalid parameters, or fopen or malloc
failed) or the number of bytes written on success. */
int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
/* Read and decode a QOI image from the file system. If channels is 0, the
number of channels from the file header is used. If channels is 3 or 4 the
output format will be forced into this number of channels.
The function either returns NULL on failure (invalid data, or malloc or fopen
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
will be filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_read(const char *filename, qoi_desc *desc, int channels);
#endif /* QOI_NO_STDIO */
/* Encode raw RGB or RGBA pixels into a QOI image in memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the encoded data on success. On success the out_len
is set to the size in bytes of the encoded data.
The returned qoi data should be free()d after use. */
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
/* Decode a QOI image from memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
is filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
#ifdef __cplusplus
}
#endif
#endif /* QOI_H */
/* -----------------------------------------------------------------------------
Implementation */
#ifdef QOI_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
#ifndef QOI_MALLOC
#define QOI_MALLOC(sz) malloc(sz)
#define QOI_FREE(p) free(p)
#endif
#ifndef QOI_ZEROARR
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
#endif
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
#define QOI_OP_RGB 0xfe /* 11111110 */
#define QOI_OP_RGBA 0xff /* 11111111 */
#define QOI_MASK_2 0xc0 /* 11000000 */
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
#define QOI_MAGIC \
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
((unsigned int)'i') << 8 | ((unsigned int)'f'))
#define QOI_HEADER_SIZE 14
/* 2GB is the max file size that this implementation can safely handle. We guard
against anything larger than that, assuming the worst case with 5 bytes per
pixel, rounded down to a nice clean value. 400 million pixels ought to be
enough for anybody. */
#define QOI_PIXELS_MAX ((unsigned int)400000000)
typedef union {
struct { unsigned char r, g, b, a; } rgba;
unsigned int v;
} qoi_rgba_t;
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
bytes[(*p)++] = (0xff000000 & v) >> 24;
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
bytes[(*p)++] = (0x000000ff & v);
}
static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
unsigned int a = bytes[(*p)++];
unsigned int b = bytes[(*p)++];
unsigned int c = bytes[(*p)++];
unsigned int d = bytes[(*p)++];
return a << 24 | b << 16 | c << 8 | d;
}
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
int i, max_size, p, run;
int px_len, px_end, px_pos, channels;
unsigned char *bytes;
const unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px, px_prev;
if (
data == NULL || out_len == NULL || desc == NULL ||
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 1 ||
desc->height >= QOI_PIXELS_MAX / desc->width
) {
return NULL;
}
max_size =
desc->width * desc->height * (desc->channels + 1) +
QOI_HEADER_SIZE + sizeof(qoi_padding);
p = 0;
bytes = (unsigned char *) QOI_MALLOC(max_size);
if (!bytes) {
return NULL;
}
qoi_write_32(bytes, &p, QOI_MAGIC);
qoi_write_32(bytes, &p, desc->width);
qoi_write_32(bytes, &p, desc->height);
bytes[p++] = desc->channels;
bytes[p++] = desc->colorspace;
pixels = (const unsigned char *)data;
QOI_ZEROARR(index);
run = 0;
px_prev.rgba.r = 0;
px_prev.rgba.g = 0;
px_prev.rgba.b = 0;
px_prev.rgba.a = 255;
px = px_prev;
px_len = desc->width * desc->height * desc->channels;
px_end = px_len - desc->channels;
channels = desc->channels;
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (channels == 4) {
px = *(qoi_rgba_t *)(pixels + px_pos);
}
else {
px.rgba.r = pixels[px_pos + 0];
px.rgba.g = pixels[px_pos + 1];
px.rgba.b = pixels[px_pos + 2];
}
if (px.v == px_prev.v) {
run++;
if (run == 62 || px_pos == px_end) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
}
else {
int index_pos;
if (run > 0) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
index_pos = QOI_COLOR_HASH(px) % 64;
if (index[index_pos].v == px.v) {
bytes[p++] = QOI_OP_INDEX | index_pos;
}
else {
index[index_pos] = px;
if (px.rgba.a == px_prev.rgba.a) {
signed char vr = px.rgba.r - px_prev.rgba.r;
signed char vg = px.rgba.g - px_prev.rgba.g;
signed char vb = px.rgba.b - px_prev.rgba.b;
signed char vg_r = vr - vg;
signed char vg_b = vb - vg;
if (
vr > -3 && vr < 2 &&
vg > -3 && vg < 2 &&
vb > -3 && vb < 2
) {
bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
}
else if (
vg_r > -9 && vg_r < 8 &&
vg > -33 && vg < 32 &&
vg_b > -9 && vg_b < 8
) {
bytes[p++] = QOI_OP_LUMA | (vg + 32);
bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
}
else {
bytes[p++] = QOI_OP_RGB;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
}
}
else {
bytes[p++] = QOI_OP_RGBA;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
bytes[p++] = px.rgba.a;
}
}
}
px_prev = px;
}
for (i = 0; i < (int)sizeof(qoi_padding); i++) {
bytes[p++] = qoi_padding[i];
}
*out_len = p;
return bytes;
}
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
const unsigned char *bytes;
unsigned int header_magic;
unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px;
int px_len, chunks_len, px_pos;
int p = 0, run = 0;
if (
data == NULL || desc == NULL ||
(channels != 0 && channels != 3 && channels != 4) ||
size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
) {
return NULL;
}
bytes = (const unsigned char *)data;
header_magic = qoi_read_32(bytes, &p);
desc->width = qoi_read_32(bytes, &p);
desc->height = qoi_read_32(bytes, &p);
desc->channels = bytes[p++];
desc->colorspace = bytes[p++];
if (
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 1 ||
header_magic != QOI_MAGIC ||
desc->height >= QOI_PIXELS_MAX / desc->width
) {
return NULL;
}
if (channels == 0) {
channels = desc->channels;
}
px_len = desc->width * desc->height * channels;
pixels = (unsigned char *) QOI_MALLOC(px_len);
if (!pixels) {
return NULL;
}
QOI_ZEROARR(index);
px.rgba.r = 0;
px.rgba.g = 0;
px.rgba.b = 0;
px.rgba.a = 255;
chunks_len = size - (int)sizeof(qoi_padding);
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (run > 0) {
run--;
}
else if (p < chunks_len) {
int b1 = bytes[p++];
if (b1 == QOI_OP_RGB) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
}
else if (b1 == QOI_OP_RGBA) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
px.rgba.a = bytes[p++];
}
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 = bytes[p++];
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;
}
if (channels == 4) {
*(qoi_rgba_t*)(pixels + px_pos) = px;
}
else {
pixels[px_pos + 0] = px.rgba.r;
pixels[px_pos + 1] = px.rgba.g;
pixels[px_pos + 2] = px.rgba.b;
}
}
return pixels;
}
#ifndef QOI_NO_STDIO
#include <stdio.h>
int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
FILE *f = fopen(filename, "wb");
int size;
void *encoded;
if (!f) {
return 0;
}
encoded = qoi_encode(data, desc, &size);
if (!encoded) {
fclose(f);
return 0;
}
fwrite(encoded, 1, size, f);
fclose(f);
QOI_FREE(encoded);
return size;
}
void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
FILE *f = fopen(filename, "rb");
int size, bytes_read;
void *pixels, *data;
if (!f) {
return NULL;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
if (size <= 0) {
fclose(f);
return NULL;
}
fseek(f, 0, SEEK_SET);
data = QOI_MALLOC(size);
if (!data) {
fclose(f);
return NULL;
}
bytes_read = (int)fread(data, 1, size, f);
fclose(f);
pixels = qoi_decode(data, bytes_read, desc, channels);
QOI_FREE(data);
return pixels;
}
#endif /* QOI_NO_STDIO */
#endif /* QOI_IMPLEMENTATION */

1212
qoirdo.cpp Normal file

File diff suppressed because it is too large Load Diff

35
qoirdo.hpp Normal file
View File

@ -0,0 +1,35 @@
#pragma once
// qoirdo.hpp
// Copyright (C) 2022 Richard Geldreich, Jr. All Rights Reserved.
// Copyright (C) 2025 Erik Scholz
// 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 <cstdint>
#include <vector>
bool init_qoi_rdo(void);
bool quit_qoi_rdo(void);
struct qoi_rdo_desc {
unsigned int width;
unsigned int height;
unsigned char channels;
unsigned char colorspace;
};
// quality 1-100
std::vector<uint8_t> encode_qoi_rdo_simple(const uint8_t* data, const qoi_rdo_desc& desc, int quality);
// TODO: finetuneable
//uint8_t* encode_qoi_rdo_advanced(const uint8_t* data, const qoi_rdo_desc* desc, int* out_len);

139
tool.cpp Normal file
View File

@ -0,0 +1,139 @@
#include "./qoirdo.hpp"
#define QOI_IMPLEMENTATION
#include "./qoi.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <ios>
#include <vector>
#include <cstdlib>
void print_help(const char* exe) {
std::cout << exe << " [-q 1-100] <path_to.qoi>\n";
}
// read qoi image, reencode lossy with rdo
int main(int argc, const char** argv) {
if (argc < 2) {
std::cerr << "error: at least one paramenter required.\n";
std::cout << "help:\n";
print_help(argv[0]);
return -1;
}
std::filesystem::path input_qoi;
int quality = 80;
if (argv[1] == std::string_view{"-q"}) {
if (argc < 4) {
std::cerr << "error: more parameters required\n";
std::cout << "help:\n";
print_help(argv[0]);
return -1;
}
quality = std::atoi(argv[2]);
if (quality < 1 || quality > 100) {
std::cerr << "error: invalid quality\n";
std::cout << "help:\n";
print_help(argv[0]);
return -1;
}
input_qoi = argv[3];
} else {
input_qoi = argv[1];
}
std::filesystem::path output_qoi;
if (input_qoi.extension() == ".qoi" || input_qoi.extension() == ".QOI") {
output_qoi = input_qoi;
output_qoi.replace_extension("rdo.qoi");
} else {
output_qoi = input_qoi;
output_qoi.replace_filename(input_qoi.filename().generic_u8string() + std::string{".rdo.qoi"});
}
std::cout << "input_qoi: " << input_qoi.generic_u8string() << "\n";
std::cout << "output_qoi: " << output_qoi.generic_u8string() << "\n";
std::cout << "quality: " << quality << "\n";
std::vector<uint8_t> input_encoded_data;
size_t input_file_size {0};
{ // read file
std::ifstream ifile{input_qoi, std::ios::in | std::ios::binary};
if (!ifile.is_open()) {
std::cerr << "failed to open file " << input_qoi << "\n";
return -2;
}
ifile.seekg(0, std::ios_base::end);
const auto size = ifile.tellg();
if (size <= 0) {
std::cerr << "failed to open file " << input_qoi << ", file too small\n";
return -2;
}
ifile.seekg(0, std::ios_base::beg);
input_encoded_data.resize(size);
ifile.read(reinterpret_cast<char*>(input_encoded_data.data()), input_encoded_data.size());
input_file_size = size;
}
// decode
qoi_desc input_desc{};
uint8_t* raw_image = static_cast<uint8_t*>(qoi_decode(input_encoded_data.data(), input_encoded_data.size(), &input_desc, 4));
if (raw_image == nullptr) {
std::cerr << "failed to decode input\n";
return -3;
}
if (input_desc.width == 0 || input_desc.height == 0) {
free(raw_image);
std::cerr << "funny trying to decode input\n";
return -3;
}
init_qoi_rdo();
// encode with rdo
qoi_rdo_desc desc{
input_desc.width,
input_desc.height,
/*input_desc.channels*/ 4, // ?
input_desc.colorspace,
};
std::vector<uint8_t> encoded_data = encode_qoi_rdo_simple(raw_image, desc,quality);
free(raw_image);
quit_qoi_rdo();
if (encoded_data.empty()) {
std::cerr << "failed to encode image\n";
return -3;
}
if (encoded_data.size() < 4) {
std::cout << "warn: encoded image suspiciously small\n";
}
{ // write out
std::ofstream ofile{output_qoi, std::ios::out | std::ios::binary | std::ios::trunc};
if (!ofile.is_open()) {
std::cerr << "failed to open output file " << output_qoi << "\n";
return -2;
}
ofile.write(reinterpret_cast<const char*>(encoded_data.data()), encoded_data.size());
}
std::cout << "written " << encoded_data.size() << " bytes to " << output_qoi << ". input was " << input_file_size << "\n";
// TODO: metrics
return 0;
}