Initial vendor packages

Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
2024-01-08 01:21:28 +04:00
parent 5ecd8cf2cb
commit 1b6a04ca55
7309 changed files with 2160054 additions and 0 deletions

1
vendor/qoi/.cargo-checksum.json vendored Normal file
View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"e6f613e9dd318adea544e29260f6c2dd72703b1f0a2c0a9609c917efd11c03c5","LICENSE-APACHE":"8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90","LICENSE-MIT":"45adbcb6d50e4b2875f22f2b997bbf6689c6825fbf7e8285a29ed35896dbfa21","README.md":"256b9e41c253272c580bc7564853f513a2656b62850907222437abb32d913a36","doc/qoi-specification.pdf":"86a3362ad7142cb1b8002f05c77ba8b11008d5f3d8c86b13a1c14bb403cfc821","ext/qoi/README.md":"75a41a1c128e26abd62cf2e99214205b3d8831ea285828f77f91a5b7e2d93c3c","ext/qoi/qoi.h":"68f8cdf35a7e0cb32bc5fe9e96d0c4f88fd0d6405c626374706bf79dbcdc4670","ext/qoi/qoibench.c":"11cd2215645ca19cd0c4d3fd8953f3478bba48909364cf35efa5a8d4b5336267","ext/qoi/qoiconv.c":"e0d5ff0bb446326109d972881870ba7e09a3ab699f0cf2109580eef6c12d3586","ext/qoi/qoifuzz.c":"fb088d0bc1d3c1afd2f9d1fb94c2b8c9374d200d31005e9b3e382031490878da","rustfmt.toml":"89171882f4be7243caf15d3bf0ad349e8adf756011ab35a72d29d19e43a8dfa3","src/consts.rs":"26c46452a2d4b9cd9c5cc704a5c211a6a458ee7cc03bd83ca3ed90fae5926bdb","src/decode.rs":"f23cc1210a4eecb795590d16bf30a8314a7a04b86e4cddf43d72a384b615629b","src/encode.rs":"73a88cc7671c904ce8e299c9cc06c83487060c792dbb90c5fdd5b6c86e5c7780","src/error.rs":"67b8d480e9f162ebc831ee511a30dd1a299f251511ef8e4059e1ce0c339bc299","src/header.rs":"3a088257e02c4f19980136392b0dce69339cc953ced77ac17609b1a302b141f9","src/lib.rs":"d7df06e0d887928ff2a82306ca22d7256991917fc488dbbdf1d7a3d1b75eadd4","src/pixel.rs":"b21e6af6eaaa40822b9df5b7696895b9d2cabe20c508bf43919e690ed81ac531","src/types.rs":"72a5172f2f74a06fc827dd731e5563129028f42fd828d1a79ca3691f1a31f0be","src/utils.rs":"b5fd3bbf35c732c4304d41a077966a7ef275a4c2e8bd42ae5e7d0b51135fa54c","tests/common.rs":"783e1ac35a08ea4efc92a9fbbfc3fe0f5622d4aade812d6070771e7843b29078","tests/test_chunks.rs":"b33ba0f1841240d14b965190874512d768cf9a8f1ebd6ce8cdd84a0df4e499a4","tests/test_gen.rs":"559eabc6324288c5e7cd4de492fc3d38c7f631ccb1f7065bd8e957d0aecaa8f8","tests/test_misc.rs":"00e2d43bdd7d105844340761a71f16309b43b84b95470d02fd9f53696a034d87","tests/test_ref.rs":"e9e6a174a54b268568b48f1bce7b8e293f50ad1f75c536d7fa1d66d104a61b9a"},"package":"7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"}

66
vendor/qoi/Cargo.toml vendored Normal file
View File

@ -0,0 +1,66 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.61.0"
name = "qoi"
version = "0.4.1"
authors = ["Ivan Smirnov <rust@ivan.smirnov.ie>"]
exclude = ["assets/*"]
description = "VERY fast encoder/decoder for QOI (Quite Okay Image) format"
homepage = "https://github.com/aldanor/qoi-rust"
documentation = "https://docs.rs/qoi"
readme = "README.md"
keywords = [
"qoi",
"graphics",
"image",
"encoding",
]
categories = [
"multimedia::images",
"multimedia::encoding",
]
license = "MIT/Apache-2.0"
repository = "https://github.com/aldanor/qoi-rust"
[profile.test]
opt-level = 3
[lib]
name = "qoi"
path = "src/lib.rs"
doctest = false
[dependencies.bytemuck]
version = "1.12"
[dev-dependencies.anyhow]
version = "1.0"
[dev-dependencies.cfg-if]
version = "1.0"
[dev-dependencies.png]
version = "0.17"
[dev-dependencies.rand]
version = "0.8"
[dev-dependencies.walkdir]
version = "2.3"
[features]
alloc = []
default = ["std"]
reference = []
std = []

201
vendor/qoi/LICENSE-APACHE vendored 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.

25
vendor/qoi/LICENSE-MIT vendored Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2022 Ivan Smirnov
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.

66
vendor/qoi/README.md vendored Normal file
View File

@ -0,0 +1,66 @@
# [qoi](https://crates.io/crates/qoi)
[![Build](https://github.com/aldanor/qoi-rust/workflows/CI/badge.svg)](https://github.com/aldanor/qoi-rust/actions?query=branch%3Amaster)
[![Latest Version](https://img.shields.io/crates/v/qoi.svg)](https://crates.io/crates/qoi)
[![Documentation](https://img.shields.io/docsrs/qoi)](https://docs.rs/qoi)
[![Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance)
Fast encoder/decoder for [QOI image format](https://qoiformat.org/), implemented in pure and safe Rust.
- One of the [fastest](#benchmarks) QOI encoders/decoders out there.
- Compliant with the [latest](https://qoiformat.org/qoi-specification.pdf) QOI format specification.
- Zero unsafe code.
- Supports decoding from / encoding to `std::io` streams directly.
- `no_std` support.
- Roundtrip-tested vs the reference C implementation; fuzz-tested.
### Examples
```rust
use qoi::{encode_to_vec, decode_to_vec};
let encoded = encode_to_vec(&pixels, width, height)?;
let (header, decoded) = decode_to_vec(&encoded)?;
assert_eq!(header.width, width);
assert_eq!(header.height, height);
assert_eq!(decoded, pixels);
```
### Benchmarks
```
decode:Mp/s encode:Mp/s decode:MB/s encode:MB/s
qoi.h 282.9 225.3 978.3 778.9
qoi-rust 427.4 290.0 1477.7 1002.9
```
- Reference C implementation:
[phoboslab/qoi@00e34217](https://github.com/phoboslab/qoi/commit/00e34217).
- Benchmark timings were collected on an Apple M1 laptop.
- 2846 images from the suite provided upstream
([tarball](https://phoboslab.org/files/qoibench/qoi_benchmark_suite.tar)):
all pngs except two with broken checksums.
- 1.32 GPixels in total with 4.46 GB of raw pixel data.
Benchmarks have also been run for all of the other Rust implementations
of QOI for comparison purposes and, at the time of writing this document,
this library proved to be the fastest one by a noticeable margin.
### Rust version
The minimum required Rust version for the latest crate version is 1.61.0.
### `no_std`
This crate supports `no_std` mode. By default, std is enabled via the `std`
feature. You can deactivate the `default-features` to target core instead.
In that case anything related to `std::io`, `std::error::Error` and heap
allocations is disabled. There is an additional `alloc` feature that can
be activated to bring back the support for heap allocations.
### License
This project is dual-licensed under MIT and Apache 2.0.

BIN
vendor/qoi/doc/qoi-specification.pdf vendored Normal file

Binary file not shown.

92
vendor/qoi/ext/qoi/README.md vendored Normal file
View File

@ -0,0 +1,92 @@
![QOI Logo](https://qoiformat.org/qoi-logo.svg)
# QOI - The “Quite OK Image Format” for fast, lossless image compression
Single-file MIT licensed library for C/C++
See [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for
the documentation and format specification.
More info at https://qoiformat.org
## Why?
Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding,
3x-4x faster decoding and 20% better compression. It's also stupidly simple and
fits in about 300 lines of C.
## Example Usage
- [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c)
converts between png <> qoi
- [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c)
a simple wrapper to benchmark stbi, libpng and qoi
## Limitations
The QOI file format allows for huge images with up to 18 exa-pixels. A streaming
en-/decoder can handle these with minimal RAM requirements, assuming there is
enough storage space.
This particular implementation of QOI however is limited to images with a
maximum size of 400 million pixels. It will safely refuse to en-/decode anything
larger than that. This is not a streaming en-/decoder. It loads the whole image
file into RAM before doing any work and is not extensively optimized for
performance (but it's still very fast).
If this is a limitation for your use case, please look into any of the other
implementations listed below.
## Tools
- https://github.com/floooh/qoiview - native QOI viewer
- https://github.com/pfusik/qoi-ci/releases/tag/qoi-ci-1.1.0 - QOI Plugin installer for GIMP, Imagine, Paint.NET and XnView MP
- https://github.com/iOrange/QoiFileTypeNet/releases/tag/v0.2 - QOI Plugin for Paint.NET
- https://github.com/iOrange/QOIThumbnailProvider - Add thumbnails for QOI images in Windows Explorer
- https://github.com/Tom94/tev - another native QOI viewer (allows pixel peeping and comparison with other image formats)
## Implementations & Bindings of QOI
- https://github.com/pfusik/qoi-ci (Ć, transpiled to C, C++, C#, Java, JavaScript, Python and Swift)
- https://github.com/kodonnell/qoi (Python)
- https://github.com/Cr4xy/lua-qoi (Lua)
- https://github.com/superzazu/SDL_QOI (C, SDL2 bindings)
- https://github.com/saharNooby/qoi-java (Java)
- https://github.com/MasterQ32/zig-qoi (Zig)
- https://github.com/rbino/qoix (Elixir)
- https://github.com/NUlliiON/QoiSharp (C#)
- https://github.com/zakarumych/rapid-qoi (Rust)
- https://github.com/takeyourhatoff/qoi (Go)
- https://github.com/DosWorld/pasqoi (Pascal)
- https://github.com/elihwyma/Swift-QOI (Swift)
- https://github.com/xfmoulet/qoi (Go)
- https://erratique.ch/software/qoic (OCaml)
## QOI Support in Other Software
- [SerenityOS](https://github.com/SerenityOS/serenity) supports decoding QOI system wide through a custom [cpp implementation in LibGfx](https://github.com/SerenityOS/serenity/blob/master/Userland/Libraries/LibGfx/QOILoader.h)
- [Raylib](https://github.com/raysan5/raylib) supports decoding and encoding QOI textures through its [rtextures module](https://github.com/raysan5/raylib/blob/master/src/rtextures.c)
- [Rebol3](https://github.com/Oldes/Rebol3/issues/39) supports decoding and encoding QOI using a native codec
- [c-ray](https://github.com/vkoskiv/c-ray) supports QOI natively
## Packages
[AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages.
## Implementations not yet conforming to the final specification
These implementations are based on the pre-release version of QOI. Resulting files are not compatible with the current version.
- https://github.com/steven-joruk/qoi (Rust)
- https://github.com/ChevyRay/qoi_rs (Rust)
- https://github.com/panzi/jsqoi (TypeScript)
- https://github.com/0xd34df00d/hsqoi (Haskell)

671
vendor/qoi/ext/qoi/qoi.h vendored Normal file
View File

@ -0,0 +1,671 @@
/*
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 7 or more consecutive QOI_OP_INDEX chunks to the
index 0, to avoid confusion with the 8 byte end marker.
.- 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 = (last_px.r - cur_px.r) - (last_px.g - cur_px.g)
db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_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
en-/decoding in any way. */
#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 = 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 */

650
vendor/qoi/ext/qoi/qoibench.c vendored Normal file
View File

@ -0,0 +1,650 @@
/*
Simple benchmark suite for png, stbi and qoi
Requires libpng, "stb_image.h" and "stb_image_write.h"
Compile with:
gcc qoibench.c -std=gnu99 -lpng -O3 -o qoibench
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.
*/
#include <stdio.h>
#include <dirent.h>
#include <png.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_LINEAR
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define QOI_IMPLEMENTATION
#include "qoi.h"
// -----------------------------------------------------------------------------
// Cross platform high resolution timer
// From https://gist.github.com/ForeverZer0/0a4f80fc02b96e19380ebb7a3debbee5
#include <stdint.h>
#if defined(__linux)
#define HAVE_POSIX_TIMER
#include <time.h>
#ifdef CLOCK_MONOTONIC
#define CLOCKID CLOCK_MONOTONIC
#else
#define CLOCKID CLOCK_REALTIME
#endif
#elif defined(__APPLE__)
#define HAVE_MACH_TIMER
#include <mach/mach_time.h>
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
static uint64_t ns() {
static uint64_t is_init = 0;
#if defined(__APPLE__)
static mach_timebase_info_data_t info;
if (0 == is_init) {
mach_timebase_info(&info);
is_init = 1;
}
uint64_t now;
now = mach_absolute_time();
now *= info.numer;
now /= info.denom;
return now;
#elif defined(__linux)
static struct timespec linux_rate;
if (0 == is_init) {
clock_getres(CLOCKID, &linux_rate);
is_init = 1;
}
uint64_t now;
struct timespec spec;
clock_gettime(CLOCKID, &spec);
now = spec.tv_sec * 1.0e9 + spec.tv_nsec;
return now;
#elif defined(_WIN32)
static LARGE_INTEGER win_frequency;
if (0 == is_init) {
QueryPerformanceFrequency(&win_frequency);
is_init = 1;
}
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return (uint64_t) ((1e9 * now.QuadPart) / win_frequency.QuadPart);
#endif
}
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define ERROR(...) printf("abort at line " TOSTRING(__LINE__) ": " __VA_ARGS__); printf("\n"); exit(1)
// -----------------------------------------------------------------------------
// libpng encode/decode wrappers
// Seriously, who thought this was a good abstraction for an API to read/write
// images?
typedef struct {
int size;
int capacity;
unsigned char *data;
} libpng_write_t;
void libpng_encode_callback(png_structp png_ptr, png_bytep data, png_size_t length) {
libpng_write_t *write_data = (libpng_write_t*)png_get_io_ptr(png_ptr);
if (write_data->size + length >= write_data->capacity) {
ERROR("PNG write");
}
memcpy(write_data->data + write_data->size, data, length);
write_data->size += length;
}
void *libpng_encode(void *pixels, int w, int h, int channels, int *out_len) {
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
ERROR("png_create_write_struct");
}
png_infop info = png_create_info_struct(png);
if (!info) {
ERROR("png_create_info_struct");
}
if (setjmp(png_jmpbuf(png))) {
ERROR("png_jmpbuf");
}
// Output is 8bit depth, RGBA format.
png_set_IHDR(
png,
info,
w, h,
8,
channels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
png_bytep row_pointers[h];
for(int y = 0; y < h; y++){
row_pointers[y] = ((unsigned char *)pixels + y * w * channels);
}
libpng_write_t write_data = {
.size = 0,
.capacity = w * h * channels,
.data = malloc(w * h * channels)
};
png_set_rows(png, info, row_pointers);
png_set_write_fn(png, &write_data, libpng_encode_callback, NULL);
png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
png_destroy_write_struct(&png, &info);
*out_len = write_data.size;
return write_data.data;
}
typedef struct {
int pos;
int size;
unsigned char *data;
} libpng_read_t;
void png_decode_callback(png_structp png, png_bytep data, png_size_t length) {
libpng_read_t *read_data = (libpng_read_t*)png_get_io_ptr(png);
if (read_data->pos + length > read_data->size) {
ERROR("PNG read %d bytes at pos %d (size: %d)", length, read_data->pos, read_data->size);
}
memcpy(data, read_data->data + read_data->pos, length);
read_data->pos += length;
}
void png_warning_callback(png_structp png_ptr, png_const_charp warning_msg) {
// Ignore warnings about sRGB profiles and such.
}
void *libpng_decode(void *data, int size, int *out_w, int *out_h) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, png_warning_callback);
if (!png) {
ERROR("png_create_read_struct");
}
png_infop info = png_create_info_struct(png);
if (!info) {
ERROR("png_create_info_struct");
}
libpng_read_t read_data = {
.pos = 0,
.size = size,
.data = data
};
png_set_read_fn(png, &read_data, png_decode_callback);
png_set_sig_bytes(png, 0);
png_read_info(png, info);
png_uint_32 w, h;
int bitDepth, colorType, interlaceType;
png_get_IHDR(png, info, &w, &h, &bitDepth, &colorType, &interlaceType, NULL, NULL);
// 16 bit -> 8 bit
png_set_strip_16(png);
// 1, 2, 4 bit -> 8 bit
if (bitDepth < 8) {
png_set_packing(png);
}
if (colorType & PNG_COLOR_MASK_PALETTE) {
png_set_expand(png);
}
if (!(colorType & PNG_COLOR_MASK_COLOR)) {
png_set_gray_to_rgb(png);
}
// set paletted or RGB images with transparency to full alpha so we get RGBA
if (png_get_valid(png, info, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png);
}
// make sure every pixel has an alpha value
if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
png_set_filler(png, 255, PNG_FILLER_AFTER);
}
png_read_update_info(png, info);
unsigned char* out = malloc(w * h * 4);
*out_w = w;
*out_h = h;
// png_uint_32 rowBytes = png_get_rowbytes(png, info);
png_bytep row_pointers[h];
for (png_uint_32 row = 0; row < h; row++ ) {
row_pointers[row] = (png_bytep)(out + (row * w * 4));
}
png_read_image(png, row_pointers);
png_read_end(png, info);
png_destroy_read_struct( &png, &info, NULL);
return out;
}
// -----------------------------------------------------------------------------
// stb_image encode callback
void stbi_write_callback(void *context, void *data, int size) {
int *encoded_size = (int *)context;
*encoded_size += size;
// In theory we'd need to do another malloc(), memcpy() and free() here to
// be fair to the other decode functions...
}
// -----------------------------------------------------------------------------
// function to load a whole file into memory
void *fload(const char *path, int *out_size) {
FILE *fh = fopen(path, "rb");
if (!fh) {
ERROR("Can't open file");
}
fseek(fh, 0, SEEK_END);
int size = ftell(fh);
fseek(fh, 0, SEEK_SET);
void *buffer = malloc(size);
if (!buffer) {
ERROR("Malloc for %d bytes failed", size);
}
if (!fread(buffer, size, 1, fh)) {
ERROR("Can't read file %s", path);
}
fclose(fh);
*out_size = size;
return buffer;
}
// -----------------------------------------------------------------------------
// benchmark runner
int opt_runs = 1;
int opt_nopng = 0;
int opt_nowarmup = 0;
int opt_noverify = 0;
int opt_nodecode = 0;
int opt_noencode = 0;
int opt_norecurse = 0;
int opt_onlytotals = 0;
typedef struct {
uint64_t size;
uint64_t encode_time;
uint64_t decode_time;
} benchmark_lib_result_t;
typedef struct {
int count;
uint64_t raw_size;
uint64_t px;
int w;
int h;
benchmark_lib_result_t libpng;
benchmark_lib_result_t stbi;
benchmark_lib_result_t qoi;
} benchmark_result_t;
void benchmark_print_result(benchmark_result_t res) {
res.px /= res.count;
res.raw_size /= res.count;
res.libpng.encode_time /= res.count;
res.libpng.decode_time /= res.count;
res.libpng.size /= res.count;
res.stbi.encode_time /= res.count;
res.stbi.decode_time /= res.count;
res.stbi.size /= res.count;
res.qoi.encode_time /= res.count;
res.qoi.decode_time /= res.count;
res.qoi.size /= res.count;
double px = res.px;
printf(" decode ms encode ms decode mpps encode mpps size kb rate\n");
if (!opt_nopng) {
printf(
"libpng: %8.1f %8.1f %8.2f %8.2f %8d %4.1f%%\n",
(double)res.libpng.decode_time/1000000.0,
(double)res.libpng.encode_time/1000000.0,
(res.libpng.decode_time > 0 ? px / ((double)res.libpng.decode_time/1000.0) : 0),
(res.libpng.encode_time > 0 ? px / ((double)res.libpng.encode_time/1000.0) : 0),
res.libpng.size/1024,
((double)res.libpng.size/(double)res.raw_size) * 100.0
);
printf(
"stbi: %8.1f %8.1f %8.2f %8.2f %8d %4.1f%%\n",
(double)res.stbi.decode_time/1000000.0,
(double)res.stbi.encode_time/1000000.0,
(res.stbi.decode_time > 0 ? px / ((double)res.stbi.decode_time/1000.0) : 0),
(res.stbi.encode_time > 0 ? px / ((double)res.stbi.encode_time/1000.0) : 0),
res.stbi.size/1024,
((double)res.stbi.size/(double)res.raw_size) * 100.0
);
}
printf(
"qoi: %8.1f %8.1f %8.2f %8.2f %8d %4.1f%%\n",
(double)res.qoi.decode_time/1000000.0,
(double)res.qoi.encode_time/1000000.0,
(res.qoi.decode_time > 0 ? px / ((double)res.qoi.decode_time/1000.0) : 0),
(res.qoi.encode_time > 0 ? px / ((double)res.qoi.encode_time/1000.0) : 0),
res.qoi.size/1024,
((double)res.qoi.size/(double)res.raw_size) * 100.0
);
printf("\n");
}
// Run __VA_ARGS__ a number of times and measure the time taken. The first
// run is ignored.
#define BENCHMARK_FN(NOWARMUP, RUNS, AVG_TIME, ...) \
do { \
uint64_t time = 0; \
for (int i = NOWARMUP; i <= RUNS; i++) { \
uint64_t time_start = ns(); \
__VA_ARGS__ \
uint64_t time_end = ns(); \
if (i > 0) { \
time += time_end - time_start; \
} \
} \
AVG_TIME = time / RUNS; \
} while (0)
benchmark_result_t benchmark_image(const char *path) {
int encoded_png_size;
int encoded_qoi_size;
int w;
int h;
int channels;
// Load the encoded PNG, encoded QOI and raw pixels into memory
if(!stbi_info(path, &w, &h, &channels)) {
ERROR("Error decoding header %s", path);
}
if (channels != 3) {
channels = 4;
}
void *pixels = (void *)stbi_load(path, &w, &h, NULL, channels);
void *encoded_png = fload(path, &encoded_png_size);
void *encoded_qoi = qoi_encode(pixels, &(qoi_desc){
.width = w,
.height = h,
.channels = channels,
.colorspace = QOI_SRGB
}, &encoded_qoi_size);
if (!pixels || !encoded_qoi || !encoded_png) {
ERROR("Error decoding %s", path);
}
// Verify QOI Output
if (!opt_noverify) {
qoi_desc dc;
void *pixels_qoi = qoi_decode(encoded_qoi, encoded_qoi_size, &dc, channels);
if (memcmp(pixels, pixels_qoi, w * h * channels) != 0) {
ERROR("QOI roundtrip pixel mismatch for %s", path);
}
free(pixels_qoi);
}
benchmark_result_t res = {0};
res.count = 1;
res.raw_size = w * h * channels;
res.px = w * h;
res.w = w;
res.h = h;
// Decoding
if (!opt_nodecode) {
if (!opt_nopng) {
BENCHMARK_FN(opt_nowarmup, opt_runs, res.libpng.decode_time, {
int dec_w, dec_h;
void *dec_p = libpng_decode(encoded_png, encoded_png_size, &dec_w, &dec_h);
free(dec_p);
});
BENCHMARK_FN(opt_nowarmup, opt_runs, res.stbi.decode_time, {
int dec_w, dec_h, dec_channels;
void *dec_p = stbi_load_from_memory(encoded_png, encoded_png_size, &dec_w, &dec_h, &dec_channels, 4);
free(dec_p);
});
}
BENCHMARK_FN(opt_nowarmup, opt_runs, res.qoi.decode_time, {
qoi_desc desc;
void *dec_p = qoi_decode(encoded_qoi, encoded_qoi_size, &desc, 4);
free(dec_p);
});
}
// Encoding
if (!opt_noencode) {
if (!opt_nopng) {
BENCHMARK_FN(opt_nowarmup, opt_runs, res.libpng.encode_time, {
int enc_size;
void *enc_p = libpng_encode(pixels, w, h, channels, &enc_size);
res.libpng.size = enc_size;
free(enc_p);
});
BENCHMARK_FN(opt_nowarmup, opt_runs, res.stbi.encode_time, {
int enc_size = 0;
stbi_write_png_to_func(stbi_write_callback, &enc_size, w, h, channels, pixels, 0);
res.stbi.size = enc_size;
});
}
BENCHMARK_FN(opt_nowarmup, opt_runs, res.qoi.encode_time, {
int enc_size;
void *enc_p = qoi_encode(pixels, &(qoi_desc){
.width = w,
.height = h,
.channels = channels,
.colorspace = QOI_SRGB
}, &enc_size);
res.qoi.size = enc_size;
free(enc_p);
});
}
free(pixels);
free(encoded_png);
free(encoded_qoi);
return res;
}
void benchmark_directory(const char *path, benchmark_result_t *grand_total) {
DIR *dir = opendir(path);
if (!dir) {
ERROR("Couldn't open directory %s", path);
}
struct dirent *file;
if (!opt_norecurse) {
for (int i = 0; (file = readdir(dir)) != NULL; i++) {
if (
file->d_type & DT_DIR &&
strcmp(file->d_name, ".") != 0 &&
strcmp(file->d_name, "..") != 0
) {
char subpath[1024];
snprintf(subpath, 1024, "%s/%s", path, file->d_name);
benchmark_directory(subpath, grand_total);
}
}
rewinddir(dir);
}
float total_percentage = 0;
int total_size = 0;
benchmark_result_t dir_total = {0};
int has_shown_head = 0;
for (int i = 0; (file = readdir(dir)) != NULL; i++) {
if (strcmp(file->d_name + strlen(file->d_name) - 4, ".png") != 0) {
continue;
}
if (!has_shown_head) {
has_shown_head = 1;
printf("## Benchmarking %s/*.png -- %d runs\n\n", path, opt_runs);
}
char *file_path = malloc(strlen(file->d_name) + strlen(path)+8);
sprintf(file_path, "%s/%s", path, file->d_name);
benchmark_result_t res = benchmark_image(file_path);
if (!opt_onlytotals) {
printf("## %s size: %dx%d\n", file_path, res.w, res.h);
benchmark_print_result(res);
}
free(file_path);
dir_total.count++;
dir_total.raw_size += res.raw_size;
dir_total.px += res.px;
dir_total.libpng.encode_time += res.libpng.encode_time;
dir_total.libpng.decode_time += res.libpng.decode_time;
dir_total.libpng.size += res.libpng.size;
dir_total.stbi.encode_time += res.stbi.encode_time;
dir_total.stbi.decode_time += res.stbi.decode_time;
dir_total.stbi.size += res.stbi.size;
dir_total.qoi.encode_time += res.qoi.encode_time;
dir_total.qoi.decode_time += res.qoi.decode_time;
dir_total.qoi.size += res.qoi.size;
grand_total->count++;
grand_total->raw_size += res.raw_size;
grand_total->px += res.px;
grand_total->libpng.encode_time += res.libpng.encode_time;
grand_total->libpng.decode_time += res.libpng.decode_time;
grand_total->libpng.size += res.libpng.size;
grand_total->stbi.encode_time += res.stbi.encode_time;
grand_total->stbi.decode_time += res.stbi.decode_time;
grand_total->stbi.size += res.stbi.size;
grand_total->qoi.encode_time += res.qoi.encode_time;
grand_total->qoi.decode_time += res.qoi.decode_time;
grand_total->qoi.size += res.qoi.size;
}
closedir(dir);
if (dir_total.count > 0) {
printf("## Total for %s\n", path);
benchmark_print_result(dir_total);
}
}
int main(int argc, char **argv) {
if (argc < 3) {
printf("Usage: qoibench <iterations> <directory> [options]\n");
printf("Options:\n");
printf(" --nowarmup ... don't perform a warmup run\n");
printf(" --nopng ...... don't run png encode/decode\n");
printf(" --noverify ... don't verify qoi roundtrip\n");
printf(" --noencode ... don't run encoders\n");
printf(" --nodecode ... don't run decoders\n");
printf(" --norecurse .. don't descend into directories\n");
printf(" --onlytotals . don't print individual image results\n");
printf("Examples\n");
printf(" qoibench 10 images/textures/\n");
printf(" qoibench 1 images/textures/ --nopng --nowarmup\n");
exit(1);
}
for (int i = 3; i < argc; i++) {
if (strcmp(argv[i], "--nowarmup") == 0) { opt_nowarmup = 1; }
else if (strcmp(argv[i], "--nopng") == 0) { opt_nopng = 1; }
else if (strcmp(argv[i], "--noverify") == 0) { opt_noverify = 1; }
else if (strcmp(argv[i], "--noencode") == 0) { opt_noencode = 1; }
else if (strcmp(argv[i], "--nodecode") == 0) { opt_nodecode = 1; }
else if (strcmp(argv[i], "--norecurse") == 0) { opt_norecurse = 1; }
else if (strcmp(argv[i], "--onlytotals") == 0) { opt_onlytotals = 1; }
else { ERROR("Unknown option %s", argv[i]); }
}
opt_runs = atoi(argv[1]);
if (opt_runs <=0) {
ERROR("Invalid number of runs %d", opt_runs);
}
benchmark_result_t grand_total = {0};
benchmark_directory(argv[2], &grand_total);
if (grand_total.count > 0) {
printf("# Grand total for %s\n", argv[2]);
benchmark_print_result(grand_total);
}
else {
printf("No images found in %s\n", argv[2]);
}
return 0;
}

106
vendor/qoi/ext/qoi/qoiconv.c vendored Normal file
View File

@ -0,0 +1,106 @@
/*
Command line tool to convert between png <> qoi format
Requires "stb_image.h" and "stb_image_write.h"
Compile with:
gcc qoiconv.c -std=c99 -O3 -o qoiconv
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.
*/
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_LINEAR
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define QOI_IMPLEMENTATION
#include "qoi.h"
#define STR_ENDS_WITH(S, E) (strcmp(S + strlen(S) - (sizeof(E)-1), E) == 0)
int main(int argc, char **argv) {
if (argc < 3) {
puts("Usage: qoiconv <infile> <outfile>");
puts("Examples:");
puts(" qoiconv input.png output.qoi");
puts(" qoiconv input.qoi output.png");
exit(1);
}
void *pixels = NULL;
int w, h, channels;
if (STR_ENDS_WITH(argv[1], ".png")) {
if(!stbi_info(argv[1], &w, &h, &channels)) {
printf("Couldn't read header %s\n", argv[1]);
exit(1);
}
// Force all odd encodings to be RGBA
if(channels != 3) {
channels = 4;
}
pixels = (void *)stbi_load(argv[1], &w, &h, NULL, channels);
}
else if (STR_ENDS_WITH(argv[1], ".qoi")) {
qoi_desc desc;
pixels = qoi_read(argv[1], &desc, 0);
channels = desc.channels;
w = desc.width;
h = desc.height;
}
if (pixels == NULL) {
printf("Couldn't load/decode %s\n", argv[1]);
exit(1);
}
int encoded = 0;
if (STR_ENDS_WITH(argv[2], ".png")) {
encoded = stbi_write_png(argv[2], w, h, channels, pixels, 0);
}
else if (STR_ENDS_WITH(argv[2], ".qoi")) {
encoded = qoi_write(argv[2], pixels, &(qoi_desc){
.width = w,
.height = h,
.channels = channels,
.colorspace = QOI_SRGB
});
}
if (!encoded) {
printf("Couldn't write/encode %s\n", argv[2]);
exit(1);
}
free(pixels);
return 0;
}

51
vendor/qoi/ext/qoi/qoifuzz.c vendored Normal file
View File

@ -0,0 +1,51 @@
/*
clang fuzzing harness for qoi_decode
Compile and run with:
clang -fsanitize=address,fuzzer -g -O0 qoifuzz.c && ./a.out
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.
*/
#define QOI_IMPLEMENTATION
#include "qoi.h"
#include <stddef.h>
#include <stdint.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
int w, h;
if (size < 4) {
return 0;
}
qoi_desc desc;
void* decoded = qoi_decode((void*)(data + 4), (int)(size - 4), &desc, *((int *)data));
if (decoded != NULL) {
free(decoded);
}
return 0;
}

7
vendor/qoi/rustfmt.toml vendored Normal file
View File

@ -0,0 +1,7 @@
use_small_heuristics = "Max"
use_field_init_shorthand = true
use_try_shorthand = true
empty_item_single_line = true
edition = "2018"
unstable_features = true
fn_args_layout = "Compressed"

17
vendor/qoi/src/consts.rs vendored Normal file
View File

@ -0,0 +1,17 @@
pub const QOI_OP_INDEX: u8 = 0x00; // 00xxxxxx
pub const QOI_OP_DIFF: u8 = 0x40; // 01xxxxxx
pub const QOI_OP_LUMA: u8 = 0x80; // 10xxxxxx
pub const QOI_OP_RUN: u8 = 0xc0; // 11xxxxxx
pub const QOI_OP_RGB: u8 = 0xfe; // 11111110
pub const QOI_OP_RGBA: u8 = 0xff; // 11111111
pub const QOI_MASK_2: u8 = 0xc0; // (11)000000
pub const QOI_HEADER_SIZE: usize = 14;
pub const QOI_PADDING: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0x01]; // 7 zeros and one 0x01 marker
pub const QOI_PADDING_SIZE: usize = 8;
pub const QOI_MAGIC: u32 = u32::from_be_bytes(*b"qoif");
pub const QOI_PIXELS_MAX: usize = 400_000_000;

396
vendor/qoi/src/decode.rs vendored Normal file
View File

@ -0,0 +1,396 @@
#[cfg(any(feature = "std", feature = "alloc"))]
use alloc::{vec, vec::Vec};
#[cfg(feature = "std")]
use std::io::Read;
// TODO: can be removed once https://github.com/rust-lang/rust/issues/74985 is stable
use bytemuck::{cast_slice_mut, Pod};
use crate::consts::{
QOI_HEADER_SIZE, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA, QOI_OP_RUN,
QOI_PADDING, QOI_PADDING_SIZE,
};
use crate::error::{Error, Result};
use crate::header::Header;
use crate::pixel::{Pixel, SupportedChannels};
use crate::types::Channels;
use crate::utils::{cold, unlikely};
const QOI_OP_INDEX_END: u8 = QOI_OP_INDEX | 0x3f;
const QOI_OP_RUN_END: u8 = QOI_OP_RUN | 0x3d; // <- note, 0x3d (not 0x3f)
const QOI_OP_DIFF_END: u8 = QOI_OP_DIFF | 0x3f;
const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f;
#[inline]
fn decode_impl_slice<const N: usize, const RGBA: bool>(data: &[u8], out: &mut [u8]) -> Result<usize>
where
Pixel<N>: SupportedChannels,
[u8; N]: Pod,
{
let mut pixels = cast_slice_mut::<_, [u8; N]>(out);
let data_len = data.len();
let mut data = data;
let mut index = [Pixel::<4>::new(); 256];
let mut px = Pixel::<N>::new().with_a(0xff);
let mut px_rgba: Pixel<4>;
while let [px_out, ptail @ ..] = pixels {
pixels = ptail;
match data {
[b1 @ QOI_OP_INDEX..=QOI_OP_INDEX_END, dtail @ ..] => {
px_rgba = index[*b1 as usize];
px.update(px_rgba);
*px_out = px.into();
data = dtail;
continue;
}
[QOI_OP_RGB, r, g, b, dtail @ ..] => {
px.update_rgb(*r, *g, *b);
data = dtail;
}
[QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => {
px.update_rgba(*r, *g, *b, *a);
data = dtail;
}
[b1 @ QOI_OP_RUN..=QOI_OP_RUN_END, dtail @ ..] => {
*px_out = px.into();
let run = ((b1 & 0x3f) as usize).min(pixels.len());
let (phead, ptail) = pixels.split_at_mut(run); // can't panic
phead.fill(px.into());
pixels = ptail;
data = dtail;
continue;
}
[b1 @ QOI_OP_DIFF..=QOI_OP_DIFF_END, dtail @ ..] => {
px.update_diff(*b1);
data = dtail;
}
[b1 @ QOI_OP_LUMA..=QOI_OP_LUMA_END, b2, dtail @ ..] => {
px.update_luma(*b1, *b2);
data = dtail;
}
_ => {
cold();
if unlikely(data.len() < QOI_PADDING_SIZE) {
return Err(Error::UnexpectedBufferEnd);
}
}
}
px_rgba = px.as_rgba(0xff);
index[px_rgba.hash_index() as usize] = px_rgba;
*px_out = px.into();
}
if unlikely(data.len() < QOI_PADDING_SIZE) {
return Err(Error::UnexpectedBufferEnd);
} else if unlikely(data[..QOI_PADDING_SIZE] != QOI_PADDING) {
return Err(Error::InvalidPadding);
}
Ok(data_len.saturating_sub(data.len()).saturating_sub(QOI_PADDING_SIZE))
}
#[inline]
fn decode_impl_slice_all(
data: &[u8], out: &mut [u8], channels: u8, src_channels: u8,
) -> Result<usize> {
match (channels, src_channels) {
(3, 3) => decode_impl_slice::<3, false>(data, out),
(3, 4) => decode_impl_slice::<3, true>(data, out),
(4, 3) => decode_impl_slice::<4, false>(data, out),
(4, 4) => decode_impl_slice::<4, true>(data, out),
_ => {
cold();
Err(Error::InvalidChannels { channels })
}
}
}
/// Decode the image into a pre-allocated buffer.
///
/// Note: the resulting number of channels will match the header. In order to change
/// the number of channels, use [`Decoder::with_channels`].
#[inline]
pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> {
let mut decoder = Decoder::new(&data)?;
decoder.decode_to_buf(buf)?;
Ok(*decoder.header())
}
/// Decode the image into a newly allocated vector.
///
/// Note: the resulting number of channels will match the header. In order to change
/// the number of channels, use [`Decoder::with_channels`].
#[cfg(any(feature = "std", feature = "alloc"))]
#[inline]
pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> {
let mut decoder = Decoder::new(&data)?;
let out = decoder.decode_to_vec()?;
Ok((*decoder.header(), out))
}
/// Decode the image header from a slice of bytes.
#[inline]
pub fn decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
Header::decode(data)
}
#[cfg(any(feature = "std"))]
#[inline]
fn decode_impl_stream<R: Read, const N: usize, const RGBA: bool>(
data: &mut R, out: &mut [u8],
) -> Result<()>
where
Pixel<N>: SupportedChannels,
[u8; N]: Pod,
{
let mut pixels = cast_slice_mut::<_, [u8; N]>(out);
let mut index = [Pixel::<N>::new(); 256];
let mut px = Pixel::<N>::new().with_a(0xff);
while let [px_out, ptail @ ..] = pixels {
pixels = ptail;
let mut p = [0];
data.read_exact(&mut p)?;
let [b1] = p;
match b1 {
QOI_OP_INDEX..=QOI_OP_INDEX_END => {
px = index[b1 as usize];
*px_out = px.into();
continue;
}
QOI_OP_RGB => {
let mut p = [0; 3];
data.read_exact(&mut p)?;
px.update_rgb(p[0], p[1], p[2]);
}
QOI_OP_RGBA if RGBA => {
let mut p = [0; 4];
data.read_exact(&mut p)?;
px.update_rgba(p[0], p[1], p[2], p[3]);
}
QOI_OP_RUN..=QOI_OP_RUN_END => {
*px_out = px.into();
let run = ((b1 & 0x3f) as usize).min(pixels.len());
let (phead, ptail) = pixels.split_at_mut(run); // can't panic
phead.fill(px.into());
pixels = ptail;
continue;
}
QOI_OP_DIFF..=QOI_OP_DIFF_END => {
px.update_diff(b1);
}
QOI_OP_LUMA..=QOI_OP_LUMA_END => {
let mut p = [0];
data.read_exact(&mut p)?;
let [b2] = p;
px.update_luma(b1, b2);
}
_ => {
cold();
}
}
index[px.hash_index() as usize] = px;
*px_out = px.into();
}
let mut p = [0_u8; QOI_PADDING_SIZE];
data.read_exact(&mut p)?;
if unlikely(p != QOI_PADDING) {
return Err(Error::InvalidPadding);
}
Ok(())
}
#[cfg(feature = "std")]
#[inline]
fn decode_impl_stream_all<R: Read>(
data: &mut R, out: &mut [u8], channels: u8, src_channels: u8,
) -> Result<()> {
match (channels, src_channels) {
(3, 3) => decode_impl_stream::<_, 3, false>(data, out),
(3, 4) => decode_impl_stream::<_, 3, true>(data, out),
(4, 3) => decode_impl_stream::<_, 4, false>(data, out),
(4, 4) => decode_impl_stream::<_, 4, true>(data, out),
_ => {
cold();
Err(Error::InvalidChannels { channels })
}
}
}
#[doc(hidden)]
pub trait Reader: Sized {
fn decode_header(&mut self) -> Result<Header>;
fn decode_image(&mut self, out: &mut [u8], channels: u8, src_channels: u8) -> Result<()>;
}
pub struct Bytes<'a>(&'a [u8]);
impl<'a> Bytes<'a> {
#[inline]
pub const fn new(buf: &'a [u8]) -> Self {
Self(buf)
}
#[inline]
pub const fn as_slice(&self) -> &[u8] {
self.0
}
}
impl<'a> Reader for Bytes<'a> {
#[inline]
fn decode_header(&mut self) -> Result<Header> {
let header = Header::decode(self.0)?;
self.0 = &self.0[QOI_HEADER_SIZE..]; // can't panic
Ok(header)
}
#[inline]
fn decode_image(&mut self, out: &mut [u8], channels: u8, src_channels: u8) -> Result<()> {
let n_read = decode_impl_slice_all(self.0, out, channels, src_channels)?;
self.0 = &self.0[n_read..];
Ok(())
}
}
#[cfg(feature = "std")]
impl<R: Read> Reader for R {
#[inline]
fn decode_header(&mut self) -> Result<Header> {
let mut b = [0; QOI_HEADER_SIZE];
self.read_exact(&mut b)?;
Header::decode(b)
}
#[inline]
fn decode_image(&mut self, out: &mut [u8], channels: u8, src_channels: u8) -> Result<()> {
decode_impl_stream_all(self, out, channels, src_channels)
}
}
/// Decode QOI images from slices or from streams.
#[derive(Clone)]
pub struct Decoder<R> {
reader: R,
header: Header,
channels: Channels,
}
impl<'a> Decoder<Bytes<'a>> {
/// Creates a new decoder from a slice of bytes.
///
/// The header will be decoded immediately upon construction.
///
/// Note: this provides the most efficient decoding, but requires the source data to
/// be loaded in memory in order to decode it. In order to decode from a generic
/// stream, use [`Decoder::from_stream`] instead.
#[inline]
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> {
Self::new_impl(Bytes::new(data.as_ref()))
}
/// Returns the undecoded tail of the input slice of bytes.
#[inline]
pub const fn data(&self) -> &[u8] {
self.reader.as_slice()
}
}
#[cfg(feature = "std")]
impl<R: Read> Decoder<R> {
/// Creates a new decoder from a generic reader that implements [`Read`](std::io::Read).
///
/// The header will be decoded immediately upon construction.
///
/// Note: while it's possible to pass a `&[u8]` slice here since it implements `Read`, it
/// would be more efficient to use a specialized constructor instead: [`Decoder::new`].
#[inline]
pub fn from_stream(reader: R) -> Result<Self> {
Self::new_impl(reader)
}
/// Returns an immutable reference to the underlying reader.
#[inline]
pub const fn reader(&self) -> &R {
&self.reader
}
/// Consumes the decoder and returns the underlying reader back.
#[inline]
#[allow(clippy::missing_const_for_fn)]
pub fn into_reader(self) -> R {
self.reader
}
}
impl<R: Reader> Decoder<R> {
#[inline]
fn new_impl(mut reader: R) -> Result<Self> {
let header = reader.decode_header()?;
Ok(Self { reader, header, channels: header.channels })
}
/// Returns a new decoder with modified number of channels.
///
/// By default, the number of channels in the decoded image will be equal
/// to whatever is specified in the header. However, it is also possible
/// to decode RGB into RGBA (in which case the alpha channel will be set
/// to 255), and vice versa (in which case the alpha channel will be ignored).
#[inline]
pub const fn with_channels(mut self, channels: Channels) -> Self {
self.channels = channels;
self
}
/// Returns the number of channels in the decoded image.
///
/// Note: this may differ from the number of channels specified in the header.
#[inline]
pub const fn channels(&self) -> Channels {
self.channels
}
/// Returns the decoded image header.
#[inline]
pub const fn header(&self) -> &Header {
&self.header
}
/// The number of bytes the decoded image will take.
///
/// Can be used to pre-allocate the buffer to decode the image into.
#[inline]
pub const fn required_buf_len(&self) -> usize {
self.header.n_pixels().saturating_mul(self.channels.as_u8() as usize)
}
/// Decodes the image to a pre-allocated buffer and returns the number of bytes written.
///
/// The minimum size of the buffer can be found via [`Decoder::required_buf_len`].
#[inline]
pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
let buf = buf.as_mut();
let size = self.required_buf_len();
if unlikely(buf.len() < size) {
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
}
self.reader.decode_image(buf, self.channels.as_u8(), self.header.channels.as_u8())?;
Ok(size)
}
/// Decodes the image into a newly allocated vector of bytes and returns it.
#[cfg(any(feature = "std", feature = "alloc"))]
#[inline]
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {
let mut out = vec![0; self.header.n_pixels() * self.channels.as_u8() as usize];
let _ = self.decode_to_buf(&mut out)?;
Ok(out)
}
}

210
vendor/qoi/src/encode.rs vendored Normal file
View File

@ -0,0 +1,210 @@
#[cfg(any(feature = "std", feature = "alloc"))]
use alloc::{vec, vec::Vec};
use core::convert::TryFrom;
#[cfg(feature = "std")]
use std::io::Write;
use bytemuck::Pod;
use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
use crate::error::{Error, Result};
use crate::header::Header;
use crate::pixel::{Pixel, SupportedChannels};
use crate::types::{Channels, ColorSpace};
#[cfg(feature = "std")]
use crate::utils::GenericWriter;
use crate::utils::{unlikely, BytesMut, Writer};
#[allow(clippy::cast_possible_truncation, unused_assignments, unused_variables)]
fn encode_impl<W: Writer, const N: usize>(mut buf: W, data: &[u8]) -> Result<usize>
where
Pixel<N>: SupportedChannels,
[u8; N]: Pod,
{
let cap = buf.capacity();
let mut index = [Pixel::new(); 256];
let mut px_prev = Pixel::new().with_a(0xff);
let mut hash_prev = px_prev.hash_index();
let mut run = 0_u8;
let mut px = Pixel::<N>::new().with_a(0xff);
let mut index_allowed = false;
let n_pixels = data.len() / N;
for (i, chunk) in data.chunks_exact(N).enumerate() {
px.read(chunk);
if px == px_prev {
run += 1;
if run == 62 || unlikely(i == n_pixels - 1) {
buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
run = 0;
}
} else {
if run != 0 {
#[cfg(not(feature = "reference"))]
{
// credits for the original idea: @zakarumych (had to be fixed though)
buf = buf.write_one(if run == 1 && index_allowed {
QOI_OP_INDEX | hash_prev
} else {
QOI_OP_RUN | (run - 1)
})?;
}
#[cfg(feature = "reference")]
{
buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
}
run = 0;
}
index_allowed = true;
let px_rgba = px.as_rgba(0xff);
hash_prev = px_rgba.hash_index();
let index_px = &mut index[hash_prev as usize];
if *index_px == px_rgba {
buf = buf.write_one(QOI_OP_INDEX | hash_prev)?;
} else {
*index_px = px_rgba;
buf = px.encode_into(px_prev, buf)?;
}
px_prev = px;
}
}
buf = buf.write_many(&QOI_PADDING)?;
Ok(cap.saturating_sub(buf.capacity()))
}
#[inline]
fn encode_impl_all<W: Writer>(out: W, data: &[u8], channels: Channels) -> Result<usize> {
match channels {
Channels::Rgb => encode_impl::<_, 3>(out, data),
Channels::Rgba => encode_impl::<_, 4>(out, data),
}
}
/// The maximum number of bytes the encoded image will take.
///
/// Can be used to pre-allocate the buffer to encode the image into.
#[inline]
pub fn encode_max_len(width: u32, height: u32, channels: impl Into<u8>) -> usize {
let (width, height) = (width as usize, height as usize);
let n_pixels = width.saturating_mul(height);
QOI_HEADER_SIZE
+ n_pixels.saturating_mul(channels.into() as usize)
+ n_pixels
+ QOI_PADDING_SIZE
}
/// Encode the image into a pre-allocated buffer.
///
/// Returns the total number of bytes written.
#[inline]
pub fn encode_to_buf(
buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
) -> Result<usize> {
Encoder::new(&data, width, height)?.encode_to_buf(buf)
}
/// Encode the image into a newly allocated vector.
#[cfg(any(feature = "alloc", feature = "std"))]
#[inline]
pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
Encoder::new(&data, width, height)?.encode_to_vec()
}
/// Encode QOI images into buffers or into streams.
pub struct Encoder<'a> {
data: &'a [u8],
header: Header,
}
impl<'a> Encoder<'a> {
/// Creates a new encoder from a given array of pixel data and image dimensions.
///
/// The number of channels will be inferred automatically (the valid values
/// are 3 or 4). The color space will be set to sRGB by default.
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
let data = data.as_ref();
let mut header =
Header::try_new(width, height, Channels::default(), ColorSpace::default())?;
let size = data.len();
let n_channels = size / header.n_pixels();
if header.n_pixels() * n_channels != size {
return Err(Error::InvalidImageLength { size, width, height });
}
header.channels = Channels::try_from(n_channels.min(0xff) as u8)?;
Ok(Self { data, header })
}
/// Returns a new encoder with modified color space.
///
/// Note: the color space doesn't affect encoding or decoding in any way, it's
/// a purely informative field that's stored in the image header.
#[inline]
pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
self.header = self.header.with_colorspace(colorspace);
self
}
/// Returns the inferred number of channels.
#[inline]
pub const fn channels(&self) -> Channels {
self.header.channels
}
/// Returns the header that will be stored in the encoded image.
#[inline]
pub const fn header(&self) -> &Header {
&self.header
}
/// The maximum number of bytes the encoded image will take.
///
/// Can be used to pre-allocate the buffer to encode the image into.
#[inline]
pub fn required_buf_len(&self) -> usize {
self.header.encode_max_len()
}
/// Encodes the image to a pre-allocated buffer and returns the number of bytes written.
///
/// The minimum size of the buffer can be found via [`Encoder::required_buf_len`].
#[inline]
pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
let buf = buf.as_mut();
let size_required = self.required_buf_len();
if unlikely(buf.len() < size_required) {
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
}
let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); // can't panic
head.copy_from_slice(&self.header.encode());
let n_written = encode_impl_all(BytesMut::new(tail), self.data, self.header.channels)?;
Ok(QOI_HEADER_SIZE + n_written)
}
/// Encodes the image into a newly allocated vector of bytes and returns it.
#[cfg(any(feature = "alloc", feature = "std"))]
#[inline]
pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
let mut out = vec![0_u8; self.required_buf_len()];
let size = self.encode_to_buf(&mut out)?;
out.truncate(size);
Ok(out)
}
/// Encodes the image directly to a generic writer that implements [`Write`](std::io::Write).
///
/// Note: while it's possible to pass a `&mut [u8]` slice here since it implements `Write`,
/// it would more effficient to use a specialized method instead: [`Encoder::encode_to_buf`].
#[cfg(feature = "std")]
#[inline]
pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {
writer.write_all(&self.header.encode())?;
let n_written =
encode_impl_all(GenericWriter::new(writer), self.data, self.header.channels)?;
Ok(n_written + QOI_HEADER_SIZE)
}
}

82
vendor/qoi/src/error.rs vendored Normal file
View File

@ -0,0 +1,82 @@
use core::convert::Infallible;
use core::fmt::{self, Display};
use crate::consts::QOI_MAGIC;
/// Errors that can occur during encoding or decoding.
#[derive(Debug)]
pub enum Error {
/// Leading 4 magic bytes don't match when decoding
InvalidMagic { magic: u32 },
/// Invalid number of channels: expected 3 or 4
InvalidChannels { channels: u8 },
/// Invalid color space: expected 0 or 1
InvalidColorSpace { colorspace: u8 },
/// Invalid image dimensions: can't be empty or larger than 400Mp
InvalidImageDimensions { width: u32, height: u32 },
/// Image dimensions are inconsistent with image buffer length
InvalidImageLength { size: usize, width: u32, height: u32 },
/// Output buffer is too small to fit encoded/decoded image
OutputBufferTooSmall { size: usize, required: usize },
/// Input buffer ended unexpectedly before decoding was finished
UnexpectedBufferEnd,
/// Invalid stream end marker encountered when decoding
InvalidPadding,
#[cfg(feature = "std")]
/// Generic I/O error from the wrapped reader/writer
IoError(std::io::Error),
}
/// Alias for [`Result`](std::result::Result) with the error type of [`Error`].
pub type Result<T> = core::result::Result<T, Error>;
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InvalidMagic { magic } => {
write!(f, "invalid magic: expected {:?}, got {:?}", QOI_MAGIC, magic.to_be_bytes())
}
Self::InvalidChannels { channels } => {
write!(f, "invalid number of channels: {}", channels)
}
Self::InvalidColorSpace { colorspace } => {
write!(f, "invalid color space: {} (expected 0 or 1)", colorspace)
}
Self::InvalidImageDimensions { width, height } => {
write!(f, "invalid image dimensions: {}x{}", width, height)
}
Self::InvalidImageLength { size, width, height } => {
write!(f, "invalid image length: {} bytes for {}x{}", size, width, height)
}
Self::OutputBufferTooSmall { size, required } => {
write!(f, "output buffer size too small: {} (required: {})", size, required)
}
Self::UnexpectedBufferEnd => {
write!(f, "unexpected input buffer end while decoding")
}
Self::InvalidPadding => {
write!(f, "invalid padding (stream end marker mismatch)")
}
#[cfg(feature = "std")]
Self::IoError(ref err) => {
write!(f, "i/o error: {}", err)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl From<Infallible> for Error {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::IoError(err)
}
}

120
vendor/qoi/src/header.rs vendored Normal file
View File

@ -0,0 +1,120 @@
use core::convert::TryInto;
use bytemuck::cast_slice;
use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX};
use crate::encode_max_len;
use crate::error::{Error, Result};
use crate::types::{Channels, ColorSpace};
use crate::utils::unlikely;
/// Image header: dimensions, channels, color space.
///
/// ### Notes
/// A valid image header must satisfy the following conditions:
/// * Both width and height must be non-zero.
/// * Maximum number of pixels is 400Mp (=4e8 pixels).
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Header {
/// Image width in pixels
pub width: u32,
/// Image height in pixels
pub height: u32,
/// Number of 8-bit channels per pixel
pub channels: Channels,
/// Color space (informative field, doesn't affect encoding)
pub colorspace: ColorSpace,
}
impl Default for Header {
#[inline]
fn default() -> Self {
Self {
width: 1,
height: 1,
channels: Channels::default(),
colorspace: ColorSpace::default(),
}
}
}
impl Header {
/// Creates a new header and validates image dimensions.
#[inline]
pub const fn try_new(
width: u32, height: u32, channels: Channels, colorspace: ColorSpace,
) -> Result<Self> {
let n_pixels = (width as usize).saturating_mul(height as usize);
if unlikely(n_pixels == 0 || n_pixels > QOI_PIXELS_MAX) {
return Err(Error::InvalidImageDimensions { width, height });
}
Ok(Self { width, height, channels, colorspace })
}
/// Creates a new header with modified channels.
#[inline]
pub const fn with_channels(mut self, channels: Channels) -> Self {
self.channels = channels;
self
}
/// Creates a new header with modified color space.
#[inline]
pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
self.colorspace = colorspace;
self
}
/// Serializes the header into a bytes array.
#[inline]
pub(crate) fn encode(&self) -> [u8; QOI_HEADER_SIZE] {
let mut out = [0; QOI_HEADER_SIZE];
out[..4].copy_from_slice(&QOI_MAGIC.to_be_bytes());
out[4..8].copy_from_slice(&self.width.to_be_bytes());
out[8..12].copy_from_slice(&self.height.to_be_bytes());
out[12] = self.channels.into();
out[13] = self.colorspace.into();
out
}
/// Deserializes the header from a byte array.
#[inline]
pub(crate) fn decode(data: impl AsRef<[u8]>) -> Result<Self> {
let data = data.as_ref();
if unlikely(data.len() < QOI_HEADER_SIZE) {
return Err(Error::UnexpectedBufferEnd);
}
let v = cast_slice::<_, [u8; 4]>(&data[..12]);
let magic = u32::from_be_bytes(v[0]);
let width = u32::from_be_bytes(v[1]);
let height = u32::from_be_bytes(v[2]);
let channels = data[12].try_into()?;
let colorspace = data[13].try_into()?;
if unlikely(magic != QOI_MAGIC) {
return Err(Error::InvalidMagic { magic });
}
Self::try_new(width, height, channels, colorspace)
}
/// Returns a number of pixels in the image.
#[inline]
pub const fn n_pixels(&self) -> usize {
(self.width as usize).saturating_mul(self.height as usize)
}
/// Returns the total number of bytes in the raw pixel array.
///
/// This may come useful when pre-allocating a buffer to decode the image into.
#[inline]
pub const fn n_bytes(&self) -> usize {
self.n_pixels() * self.channels.as_u8() as usize
}
/// The maximum number of bytes the encoded image will take.
///
/// Can be used to pre-allocate the buffer to encode the image into.
#[inline]
pub fn encode_max_len(&self) -> usize {
encode_max_len(self.width, self.height, self.channels)
}
}

95
vendor/qoi/src/lib.rs vendored Normal file
View File

@ -0,0 +1,95 @@
//! Fast encoder/decoder for [QOI image format](https://qoiformat.org/), implemented in pure and safe Rust.
//!
//! - One of the [fastest](#benchmarks) QOI encoders/decoders out there.
//! - Compliant with the [latest](https://qoiformat.org/qoi-specification.pdf) QOI format specification.
//! - Zero unsafe code.
//! - Supports decoding from / encoding to `std::io` streams directly.
//! - `no_std` support.
//! - Roundtrip-tested vs the reference C implementation; fuzz-tested.
//!
//! ### Examples
//!
//! ```rust
//! use qoi::{encode_to_vec, decode_to_vec};
//!
//! let encoded = encode_to_vec(&pixels, width, height)?;
//! let (header, decoded) = decode_to_vec(&encoded)?;
//!
//! assert_eq!(header.width, width);
//! assert_eq!(header.height, height);
//! assert_eq!(decoded, pixels);
//! ```
//!
//! ### Benchmarks
//!
//! ```
//! decode:Mp/s encode:Mp/s decode:MB/s encode:MB/s
//! qoi.h 282.9 225.3 978.3 778.9
//! qoi-rust 427.4 290.0 1477.7 1002.9
//! ```
//!
//! - Reference C implementation:
//! [phoboslab/qoi@00e34217](https://github.com/phoboslab/qoi/commit/00e34217).
//! - Benchmark timings were collected on an Apple M1 laptop.
//! - 2846 images from the suite provided upstream
//! ([tarball](https://phoboslab.org/files/qoibench/qoi_benchmark_suite.tar)):
//! all pngs except two with broken checksums.
//! - 1.32 GPixels in total with 4.46 GB of raw pixel data.
//!
//! Benchmarks have also been run for all of the other Rust implementations
//! of QOI for comparison purposes and, at the time of writing this document,
//! this library proved to be the fastest one by a noticeable margin.
//!
//! ### Rust version
//!
//! The minimum supported Rust version is 1.51.0 (any changes to this would be
//! considered to be a breaking change).
//!
//! ### `no_std`
//!
//! This crate supports `no_std` mode. By default, std is enabled via the `std`
//! feature. You can deactivate the `default-features` to target core instead.
//! In that case anything related to `std::io`, `std::error::Error` and heap
//! allocations is disabled. There is an additional `alloc` feature that can
//! be activated to bring back the support for heap allocations.
#![forbid(unsafe_code)]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(
clippy::inline_always,
clippy::similar_names,
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::module_name_repetitions,
clippy::cargo_common_metadata,
clippy::doc_markdown,
clippy::return_self_not_must_use,
)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#[cfg(all(feature = "alloc", not(any(feature = "std", test))))]
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std as alloc;
mod decode;
mod encode;
mod error;
mod header;
mod pixel;
mod types;
mod utils;
#[doc(hidden)]
pub mod consts;
#[cfg(any(feature = "alloc", feature = "std"))]
pub use crate::decode::decode_to_vec;
pub use crate::decode::{decode_header, decode_to_buf, Decoder};
#[cfg(any(feature = "alloc", feature = "std"))]
pub use crate::encode::encode_to_vec;
pub use crate::encode::{encode_max_len, encode_to_buf, Encoder};
pub use crate::error::{Error, Result};
pub use crate::header::Header;
pub use crate::types::{Channels, ColorSpace};

183
vendor/qoi/src/pixel.rs vendored Normal file
View File

@ -0,0 +1,183 @@
use crate::consts::{QOI_OP_DIFF, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA};
use crate::error::Result;
use crate::utils::Writer;
use bytemuck::{cast, Pod};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(transparent)]
pub struct Pixel<const N: usize>([u8; N]);
impl<const N: usize> Pixel<N> {
#[inline]
pub const fn new() -> Self {
Self([0; N])
}
#[inline]
pub fn read(&mut self, s: &[u8]) {
if s.len() == N {
let mut i = 0;
while i < N {
self.0[i] = s[i];
i += 1;
}
} else {
unreachable!();
}
}
#[inline]
pub fn update<const M: usize>(&mut self, px: Pixel<M>) {
let mut i = 0;
while i < M && i < N {
self.0[i] = px.0[i];
i += 1;
}
}
#[inline]
pub fn update_rgb(&mut self, r: u8, g: u8, b: u8) {
self.0[0] = r;
self.0[1] = g;
self.0[2] = b;
}
#[inline]
pub fn update_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) {
self.0[0] = r;
self.0[1] = g;
self.0[2] = b;
if N >= 4 {
self.0[3] = a;
}
}
#[inline]
pub fn update_diff(&mut self, b1: u8) {
self.0[0] = self.0[0].wrapping_add((b1 >> 4) & 0x03).wrapping_sub(2);
self.0[1] = self.0[1].wrapping_add((b1 >> 2) & 0x03).wrapping_sub(2);
self.0[2] = self.0[2].wrapping_add(b1 & 0x03).wrapping_sub(2);
}
#[inline]
pub fn update_luma(&mut self, b1: u8, b2: u8) {
let vg = (b1 & 0x3f).wrapping_sub(32);
let vg_8 = vg.wrapping_sub(8);
let vr = vg_8.wrapping_add((b2 >> 4) & 0x0f);
let vb = vg_8.wrapping_add(b2 & 0x0f);
self.0[0] = self.0[0].wrapping_add(vr);
self.0[1] = self.0[1].wrapping_add(vg);
self.0[2] = self.0[2].wrapping_add(vb);
}
#[inline]
pub const fn as_rgba(self, with_a: u8) -> Pixel<4> {
let mut i = 0;
let mut out = Pixel::new();
while i < N {
out.0[i] = self.0[i];
i += 1;
}
if N < 4 {
out.0[3] = with_a;
}
out
}
#[inline]
pub const fn r(self) -> u8 {
self.0[0]
}
#[inline]
pub const fn g(self) -> u8 {
self.0[1]
}
#[inline]
pub const fn b(self) -> u8 {
self.0[2]
}
#[inline]
pub const fn with_a(mut self, value: u8) -> Self {
if N >= 4 {
self.0[3] = value;
}
self
}
#[inline]
pub const fn a_or(self, value: u8) -> u8 {
if N < 4 {
value
} else {
self.0[3]
}
}
#[inline]
#[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
pub fn hash_index(self) -> u8
where
[u8; N]: Pod,
{
// credits for the initial idea: @zakarumych
let v = if N == 4 {
u32::from_ne_bytes(cast(self.0))
} else {
u32::from_ne_bytes([self.0[0], self.0[1], self.0[2], 0xff])
} as u64;
let s = ((v & 0xff00_ff00) << 32) | (v & 0x00ff_00ff);
s.wrapping_mul(0x0300_0700_0005_000b_u64).to_le().swap_bytes() as u8 & 63
}
#[inline]
pub fn rgb_add(&mut self, r: u8, g: u8, b: u8) {
self.0[0] = self.0[0].wrapping_add(r);
self.0[1] = self.0[1].wrapping_add(g);
self.0[2] = self.0[2].wrapping_add(b);
}
#[inline]
pub fn encode_into<W: Writer>(&self, px_prev: Self, buf: W) -> Result<W> {
if N == 3 || self.a_or(0) == px_prev.a_or(0) {
let vg = self.g().wrapping_sub(px_prev.g());
let vg_32 = vg.wrapping_add(32);
if vg_32 | 63 == 63 {
let vr = self.r().wrapping_sub(px_prev.r());
let vb = self.b().wrapping_sub(px_prev.b());
let vg_r = vr.wrapping_sub(vg);
let vg_b = vb.wrapping_sub(vg);
let (vr_2, vg_2, vb_2) =
(vr.wrapping_add(2), vg.wrapping_add(2), vb.wrapping_add(2));
if vr_2 | vg_2 | vb_2 | 3 == 3 {
buf.write_one(QOI_OP_DIFF | vr_2 << 4 | vg_2 << 2 | vb_2)
} else {
let (vg_r_8, vg_b_8) = (vg_r.wrapping_add(8), vg_b.wrapping_add(8));
if vg_r_8 | vg_b_8 | 15 == 15 {
buf.write_many(&[QOI_OP_LUMA | vg_32, vg_r_8 << 4 | vg_b_8])
} else {
buf.write_many(&[QOI_OP_RGB, self.r(), self.g(), self.b()])
}
}
} else {
buf.write_many(&[QOI_OP_RGB, self.r(), self.g(), self.b()])
}
} else {
buf.write_many(&[QOI_OP_RGBA, self.r(), self.g(), self.b(), self.a_or(0xff)])
}
}
}
impl<const N: usize> From<Pixel<N>> for [u8; N] {
#[inline(always)]
fn from(px: Pixel<N>) -> Self {
px.0
}
}
pub trait SupportedChannels {}
impl SupportedChannels for Pixel<3> {}
impl SupportedChannels for Pixel<4> {}

113
vendor/qoi/src/types.rs vendored Normal file
View File

@ -0,0 +1,113 @@
use core::convert::TryFrom;
use crate::error::{Error, Result};
use crate::utils::unlikely;
/// Image color space.
///
/// Note: the color space is purely informative. Although it is saved to the
/// file header, it does not affect encoding/decoding in any way.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
#[repr(u8)]
pub enum ColorSpace {
/// sRGB with linear alpha
Srgb = 0,
/// All channels are linear
Linear = 1,
}
impl ColorSpace {
/// Returns true if the color space is sRGB with linear alpha.
pub const fn is_srgb(self) -> bool {
matches!(self, Self::Srgb)
}
/// Returns true is all channels are linear.
pub const fn is_linear(self) -> bool {
matches!(self, Self::Linear)
}
/// Converts to an integer (0 if sRGB, 1 if all linear).
pub const fn as_u8(self) -> u8 {
self as u8
}
}
impl Default for ColorSpace {
fn default() -> Self {
Self::Srgb
}
}
impl From<ColorSpace> for u8 {
#[inline]
fn from(colorspace: ColorSpace) -> Self {
colorspace as Self
}
}
impl TryFrom<u8> for ColorSpace {
type Error = Error;
#[inline]
fn try_from(colorspace: u8) -> Result<Self> {
if unlikely(colorspace | 1 != 1) {
Err(Error::InvalidColorSpace { colorspace })
} else {
Ok(if colorspace == 0 { Self::Srgb } else { Self::Linear })
}
}
}
/// Number of 8-bit channels in a pixel.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
#[repr(u8)]
pub enum Channels {
/// Three 8-bit channels (RGB)
Rgb = 3,
/// Four 8-bit channels (RGBA)
Rgba = 4,
}
impl Channels {
/// Returns true if there are 3 channels (RGB).
pub const fn is_rgb(self) -> bool {
matches!(self, Self::Rgb)
}
/// Returns true if there are 4 channels (RGBA).
pub const fn is_rgba(self) -> bool {
matches!(self, Self::Rgba)
}
/// Converts to an integer (3 if RGB, 4 if RGBA).
pub const fn as_u8(self) -> u8 {
self as u8
}
}
impl Default for Channels {
fn default() -> Self {
Self::Rgb
}
}
impl From<Channels> for u8 {
#[inline]
fn from(channels: Channels) -> Self {
channels as Self
}
}
impl TryFrom<u8> for Channels {
type Error = Error;
#[inline]
fn try_from(channels: u8) -> Result<Self> {
if unlikely(channels != 3 && channels != 4) {
Err(Error::InvalidChannels { channels })
} else {
Ok(if channels == 3 { Self::Rgb } else { Self::Rgba })
}
}
}

107
vendor/qoi/src/utils.rs vendored Normal file
View File

@ -0,0 +1,107 @@
#[cfg(feature = "std")]
use std::io::Write;
use crate::error::Result;
#[inline(always)]
#[cold]
pub const fn cold() {}
#[inline(always)]
#[allow(unused)]
pub const fn likely(b: bool) -> bool {
if !b {
cold();
}
b
}
#[inline(always)]
pub const fn unlikely(b: bool) -> bool {
if b {
cold();
}
b
}
pub trait Writer: Sized {
fn write_one(self, v: u8) -> Result<Self>;
fn write_many(self, v: &[u8]) -> Result<Self>;
fn capacity(&self) -> usize;
}
pub struct BytesMut<'a>(&'a mut [u8]);
impl<'a> BytesMut<'a> {
pub fn new(buf: &'a mut [u8]) -> Self {
Self(buf)
}
#[inline]
pub fn write_one(self, v: u8) -> Self {
if let Some((first, tail)) = self.0.split_first_mut() {
*first = v;
Self(tail)
} else {
unreachable!()
}
}
#[inline]
pub fn write_many(self, v: &[u8]) -> Self {
if v.len() <= self.0.len() {
let (head, tail) = self.0.split_at_mut(v.len());
head.copy_from_slice(v);
Self(tail)
} else {
unreachable!()
}
}
}
impl<'a> Writer for BytesMut<'a> {
#[inline]
fn write_one(self, v: u8) -> Result<Self> {
Ok(BytesMut::write_one(self, v))
}
#[inline]
fn write_many(self, v: &[u8]) -> Result<Self> {
Ok(BytesMut::write_many(self, v))
}
#[inline]
fn capacity(&self) -> usize {
self.0.len()
}
}
#[cfg(feature = "std")]
pub struct GenericWriter<W> {
writer: W,
n_written: usize,
}
#[cfg(feature = "std")]
impl<W: Write> GenericWriter<W> {
pub const fn new(writer: W) -> Self {
Self { writer, n_written: 0 }
}
}
#[cfg(feature = "std")]
impl<W: Write> Writer for GenericWriter<W> {
fn write_one(mut self, v: u8) -> Result<Self> {
self.n_written += 1;
self.writer.write_all(&[v]).map(|_| self).map_err(Into::into)
}
fn write_many(mut self, v: &[u8]) -> Result<Self> {
self.n_written += v.len();
self.writer.write_all(v).map(|_| self).map_err(Into::into)
}
fn capacity(&self) -> usize {
usize::MAX - self.n_written
}
}

12
vendor/qoi/tests/common.rs vendored Normal file
View File

@ -0,0 +1,12 @@
#[allow(unused)]
pub fn hash<const N: usize>(px: [u8; N]) -> u8 {
let r = px[0];
let g = px[1];
let b = px[2];
let a = if N >= 4 { px[3] } else { 0xff };
let rm = r.wrapping_mul(3);
let gm = g.wrapping_mul(5);
let bm = b.wrapping_mul(7);
let am = a.wrapping_mul(11);
rm.wrapping_add(gm).wrapping_add(bm).wrapping_add(am) % 64
}

211
vendor/qoi/tests/test_chunks.rs vendored Normal file
View File

@ -0,0 +1,211 @@
mod common;
use bytemuck::{cast_slice, Pod};
use qoi::consts::{
QOI_HEADER_SIZE, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA, QOI_OP_RUN,
QOI_PADDING_SIZE,
};
use qoi::{decode_to_vec, encode_to_vec};
use self::common::hash;
fn test_chunk<P, E, const N: usize>(pixels: P, expected: E)
where
P: AsRef<[[u8; N]]>,
E: AsRef<[u8]>,
[u8; N]: Pod,
{
let pixels = pixels.as_ref();
let expected = expected.as_ref();
let pixels_raw = cast_slice::<_, u8>(pixels);
let encoded = encode_to_vec(pixels_raw, pixels.len() as _, 1).unwrap();
let decoded = decode_to_vec(&encoded).unwrap().1;
assert_eq!(pixels_raw, decoded.as_slice(), "roundtrip failed (encoded={:?}))", encoded);
assert!(encoded.len() >= expected.len() + QOI_HEADER_SIZE + QOI_PADDING_SIZE);
assert_eq!(&encoded[QOI_HEADER_SIZE..][..expected.len()], expected);
}
#[test]
fn test_encode_rgb_3ch() {
test_chunk([[11, 121, 231]], [QOI_OP_RGB, 11, 121, 231]);
}
#[test]
fn test_encode_rgb_4ch() {
test_chunk([[11, 121, 231, 0xff]], [QOI_OP_RGB, 11, 121, 231]);
}
#[test]
fn test_encode_rgba() {
test_chunk([[11, 121, 231, 55]], [QOI_OP_RGBA, 11, 121, 231, 55]);
}
#[test]
fn test_encode_run_start_len1to62_3ch() {
for n in 1..=62 {
let mut v = vec![[0, 0, 0]; n];
v.push([11, 22, 33]);
test_chunk(v, [QOI_OP_RUN | (n as u8 - 1), QOI_OP_RGB]);
}
}
#[test]
fn test_encode_run_start_len1to62_4ch() {
for n in 1..=62 {
let mut v = vec![[0, 0, 0, 0xff]; n];
v.push([11, 22, 33, 44]);
test_chunk(v, [QOI_OP_RUN | (n as u8 - 1), QOI_OP_RGBA]);
}
}
#[test]
fn test_encode_run_start_63to124_3ch() {
for n in 63..=124 {
let mut v = vec![[0, 0, 0]; n];
v.push([11, 22, 33]);
test_chunk(v, [QOI_OP_RUN | 61, QOI_OP_RUN | (n as u8 - 63), QOI_OP_RGB]);
}
}
#[test]
fn test_encode_run_start_len63to124_4ch() {
for n in 63..=124 {
let mut v = vec![[0, 0, 0, 0xff]; n];
v.push([11, 22, 33, 44]);
test_chunk(v, [QOI_OP_RUN | 61, QOI_OP_RUN | (n as u8 - 63), QOI_OP_RGBA]);
}
}
#[test]
fn test_encode_run_end_3ch() {
let px = [11, 33, 55];
test_chunk(
[[1, 99, 2], px, px, px],
[QOI_OP_RGB, 1, 99, 2, QOI_OP_RGB, px[0], px[1], px[2], QOI_OP_RUN | 1],
);
}
#[test]
fn test_encode_run_end_4ch() {
let px = [11, 33, 55, 77];
test_chunk(
[[1, 99, 2, 3], px, px, px],
[QOI_OP_RGBA, 1, 99, 2, 3, QOI_OP_RGBA, px[0], px[1], px[2], px[3], QOI_OP_RUN | 1],
);
}
#[test]
fn test_encode_run_mid_3ch() {
let px = [11, 33, 55];
test_chunk(
[[1, 99, 2], px, px, px, [1, 2, 3]],
[QOI_OP_RGB, 1, 99, 2, QOI_OP_RGB, px[0], px[1], px[2], QOI_OP_RUN | 1],
);
}
#[test]
fn test_encode_run_mid_4ch() {
let px = [11, 33, 55, 77];
test_chunk(
[[1, 99, 2, 3], px, px, px, [1, 2, 3, 4]],
[QOI_OP_RGBA, 1, 99, 2, 3, QOI_OP_RGBA, px[0], px[1], px[2], px[3], QOI_OP_RUN | 1],
);
}
#[test]
fn test_encode_index_3ch() {
let px = [101, 102, 103];
test_chunk(
[px, [1, 2, 3], px],
[QOI_OP_RGB, 101, 102, 103, QOI_OP_RGB, 1, 2, 3, QOI_OP_INDEX | hash(px)],
);
}
#[test]
fn test_encode_index_4ch() {
let px = [101, 102, 103, 104];
test_chunk(
[px, [1, 2, 3, 4], px],
[QOI_OP_RGBA, 101, 102, 103, 104, QOI_OP_RGBA, 1, 2, 3, 4, QOI_OP_INDEX | hash(px)],
);
}
#[test]
fn test_encode_index_zero_3ch() {
let px = [0, 0, 0];
test_chunk([[101, 102, 103], px], [QOI_OP_RGB, 101, 102, 103, QOI_OP_RGB, 0, 0, 0]);
}
#[test]
fn test_encode_index_zero_0x00_4ch() {
let px = [0, 0, 0, 0];
test_chunk(
[[101, 102, 103, 104], px],
[QOI_OP_RGBA, 101, 102, 103, 104, QOI_OP_INDEX | hash(px)],
);
}
#[test]
fn test_encode_index_zero_0xff_4ch() {
let px = [0, 0, 0, 0xff];
test_chunk(
[[101, 102, 103, 104], px],
[QOI_OP_RGBA, 101, 102, 103, 104, QOI_OP_RGBA, 0, 0, 0, 0xff],
);
}
#[test]
fn test_encode_diff() {
for x in 0..8_u8 {
let x = [x.wrapping_sub(5), x.wrapping_sub(4), x.wrapping_sub(3)];
for dr in 0..3 {
for dg in 0..3 {
for db in 0..3 {
if dr != 2 || dg != 2 || db != 2 {
let r = x[0].wrapping_add(dr).wrapping_sub(2);
let g = x[1].wrapping_add(dg).wrapping_sub(2);
let b = x[2].wrapping_add(db).wrapping_sub(2);
let d = QOI_OP_DIFF | dr << 4 | dg << 2 | db;
test_chunk(
[[1, 99, 2], x, [r, g, b]],
[QOI_OP_RGB, 1, 99, 2, QOI_OP_RGB, x[0], x[1], x[2], d],
);
test_chunk(
[[1, 99, 2, 0xff], [x[0], x[1], x[2], 9], [r, g, b, 9]],
[QOI_OP_RGB, 1, 99, 2, QOI_OP_RGBA, x[0], x[1], x[2], 9, d],
);
}
}
}
}
}
}
#[test]
fn test_encode_luma() {
for x in (0..200_u8).step_by(4) {
let x = [x.wrapping_mul(3), x.wrapping_sub(5), x.wrapping_sub(7)];
for dr_g in (0..16).step_by(4) {
for dg in (0..64).step_by(8) {
for db_g in (0..16).step_by(4) {
if dr_g != 8 || dg != 32 || db_g != 8 {
let r = x[0].wrapping_add(dr_g).wrapping_add(dg).wrapping_sub(40);
let g = x[1].wrapping_add(dg).wrapping_sub(32);
let b = x[2].wrapping_add(db_g).wrapping_add(dg).wrapping_sub(40);
let d1 = QOI_OP_LUMA | dg;
let d2 = (dr_g << 4) | db_g;
test_chunk(
[[1, 99, 2], x, [r, g, b]],
[QOI_OP_RGB, 1, 99, 2, QOI_OP_RGB, x[0], x[1], x[2], d1, d2],
);
test_chunk(
[[1, 99, 2, 0xff], [x[0], x[1], x[2], 9], [r, g, b, 9]],
[QOI_OP_RGB, 1, 99, 2, QOI_OP_RGBA, x[0], x[1], x[2], 9, d1, d2],
);
}
}
}
}
}
}

313
vendor/qoi/tests/test_gen.rs vendored Normal file
View File

@ -0,0 +1,313 @@
mod common;
use bytemuck::cast_slice;
use std::borrow::Cow;
use std::fmt::Debug;
use cfg_if::cfg_if;
use rand::{
distributions::{Distribution, Standard},
rngs::StdRng,
Rng, SeedableRng,
};
use libqoi::{qoi_decode, qoi_encode};
use qoi::consts::{
QOI_HEADER_SIZE, QOI_MASK_2, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA,
QOI_OP_RUN, QOI_PADDING_SIZE,
};
use qoi::{decode_header, decode_to_vec, encode_to_vec};
use self::common::hash;
struct GenState<const N: usize> {
index: [[u8; N]; 64],
pixels: Vec<u8>,
prev: [u8; N],
len: usize,
}
impl<const N: usize> GenState<N> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
index: [[0; N]; 64],
pixels: Vec::with_capacity(capacity * N),
prev: Self::zero(),
len: 0,
}
}
pub fn write(&mut self, px: [u8; N]) {
self.index[hash(px) as usize] = px;
for i in 0..N {
self.pixels.push(px[i]);
}
self.prev = px;
self.len += 1;
}
pub fn pick_from_index(&self, rng: &mut impl Rng) -> [u8; N] {
self.index[rng.gen_range(0_usize..64)]
}
pub fn zero() -> [u8; N] {
let mut px = [0; N];
if N >= 4 {
px[3] = 0xff;
}
px
}
}
struct ImageGen {
p_new: f64,
p_index: f64,
p_repeat: f64,
p_diff: f64,
p_luma: f64,
}
impl ImageGen {
pub fn new_random(rng: &mut impl Rng) -> Self {
let p: [f64; 6] = rng.gen();
let t = p.iter().sum::<f64>();
Self {
p_new: p[0] / t,
p_index: p[1] / t,
p_repeat: p[2] / t,
p_diff: p[3] / t,
p_luma: p[4] / t,
}
}
pub fn generate(&self, rng: &mut impl Rng, channels: usize, min_len: usize) -> Vec<u8> {
match channels {
3 => self.generate_const::<_, 3>(rng, min_len),
4 => self.generate_const::<_, 4>(rng, min_len),
_ => panic!(),
}
}
fn generate_const<R: Rng, const N: usize>(&self, rng: &mut R, min_len: usize) -> Vec<u8>
where
Standard: Distribution<[u8; N]>,
{
let mut s = GenState::<N>::with_capacity(min_len);
let zero = GenState::<N>::zero();
while s.len < min_len {
let mut p = rng.gen_range(0.0..1.0);
if p < self.p_new {
s.write(rng.gen());
continue;
}
p -= self.p_new;
if p < self.p_index {
let px = s.pick_from_index(rng);
s.write(px);
continue;
}
p -= self.p_index;
if p < self.p_repeat {
let px = s.prev;
let n_repeat = rng.gen_range(1_usize..=70);
for _ in 0..n_repeat {
s.write(px);
}
continue;
}
p -= self.p_repeat;
if p < self.p_diff {
let mut px = s.prev;
px[0] = px[0].wrapping_add(rng.gen_range(0_u8..4).wrapping_sub(2));
px[1] = px[1].wrapping_add(rng.gen_range(0_u8..4).wrapping_sub(2));
px[2] = px[2].wrapping_add(rng.gen_range(0_u8..4).wrapping_sub(2));
s.write(px);
continue;
}
p -= self.p_diff;
if p < self.p_luma {
let mut px = s.prev;
let vg = rng.gen_range(0_u8..64).wrapping_sub(32);
let vr = rng.gen_range(0_u8..16).wrapping_sub(8).wrapping_add(vg);
let vb = rng.gen_range(0_u8..16).wrapping_sub(8).wrapping_add(vg);
px[0] = px[0].wrapping_add(vr);
px[1] = px[1].wrapping_add(vg);
px[2] = px[2].wrapping_add(vb);
s.write(px);
continue;
}
s.write(zero);
}
s.pixels
}
}
fn format_encoded(encoded: &[u8]) -> String {
let header = decode_header(encoded).unwrap();
let mut data = &encoded[QOI_HEADER_SIZE..encoded.len() - QOI_PADDING_SIZE];
let mut s = format!("{}x{}:{} = [", header.width, header.height, header.channels.as_u8());
while !data.is_empty() {
let b1 = data[0];
data = &data[1..];
match b1 {
QOI_OP_RGB => {
s.push_str(&format!("rgb({},{},{})", data[0], data[1], data[2]));
data = &data[3..];
}
QOI_OP_RGBA => {
s.push_str(&format!("rgba({},{},{},{})", data[0], data[1], data[2], data[3]));
data = &data[4..];
}
_ => match b1 & QOI_MASK_2 {
QOI_OP_INDEX => s.push_str(&format!("index({})", b1 & 0x3f)),
QOI_OP_RUN => s.push_str(&format!("run({})", b1 & 0x3f)),
QOI_OP_DIFF => s.push_str(&format!(
"diff({},{},{})",
(b1 >> 4) & 0x03,
(b1 >> 2) & 0x03,
b1 & 0x03
)),
QOI_OP_LUMA => {
let b2 = data[0];
data = &data[1..];
s.push_str(&format!("luma({},{},{})", (b2 >> 4) & 0x0f, b1 & 0x3f, b2 & 0x0f))
}
_ => {}
},
}
s.push_str(", ");
}
s.pop().unwrap();
s.pop().unwrap();
s.push(']');
s
}
fn check_roundtrip<E, D, VE, VD, EE, ED>(
msg: &str, mut data: &[u8], channels: usize, encode: E, decode: D,
) where
E: Fn(&[u8], u32) -> Result<VE, EE>,
D: Fn(&[u8]) -> Result<VD, ED>,
VE: AsRef<[u8]>,
VD: AsRef<[u8]>,
EE: Debug,
ED: Debug,
{
macro_rules! rt {
($data:expr, $n:expr) => {
decode(encode($data, $n as _).unwrap().as_ref()).unwrap()
};
}
macro_rules! fail {
($msg:expr, $data:expr, $decoded:expr, $encoded:expr, $channels:expr) => {
assert!(
false,
"{} roundtrip failed\n\n image: {:?}\ndecoded: {:?}\nencoded: {}",
$msg,
cast_slice::<_, [u8; $channels]>($data.as_ref()),
cast_slice::<_, [u8; $channels]>($decoded.as_ref()),
format_encoded($encoded.as_ref()),
);
};
}
let mut n_pixels = data.len() / channels;
assert_eq!(n_pixels * channels, data.len());
// if all ok, return
// ... but if roundtrip check fails, try to reduce the example to the smallest we can find
if rt!(data, n_pixels).as_ref() == data {
return;
}
// try removing pixels from the beginning
while n_pixels > 1 {
let slice = &data[..data.len() - channels];
if rt!(slice, n_pixels - 1).as_ref() != slice {
data = slice;
n_pixels -= 1;
} else {
break;
}
}
// try removing pixels from the end
while n_pixels > 1 {
let slice = &data[channels..];
if rt!(slice, n_pixels - 1).as_ref() != slice {
data = slice;
n_pixels -= 1;
} else {
break;
}
}
// try removing pixels from the middle
let mut data = Cow::from(data);
let mut pos = 1;
while n_pixels > 1 && pos < n_pixels - 1 {
let mut vec = data.to_vec();
for _ in 0..channels {
vec.remove(pos * channels);
}
if rt!(vec.as_slice(), n_pixels - 1).as_ref() != vec.as_slice() {
data = Cow::from(vec);
n_pixels -= 1;
} else {
pos += 1;
}
}
let encoded = encode(data.as_ref(), n_pixels as _).unwrap();
let decoded = decode(encoded.as_ref()).unwrap();
assert_ne!(decoded.as_ref(), data.as_ref());
if channels == 3 {
fail!(msg, data, decoded, encoded, 3);
} else {
fail!(msg, data, decoded, encoded, 4);
}
}
#[test]
fn test_generated() {
let mut rng = StdRng::seed_from_u64(0);
let mut n_pixels = 0;
while n_pixels < 20_000_000 {
let min_len = rng.gen_range(1..=5000);
let channels = rng.gen_range(3..=4);
let gen = ImageGen::new_random(&mut rng);
let img = gen.generate(&mut rng, channels, min_len);
let encode = |data: &[u8], size| encode_to_vec(data, size, 1);
let decode = |data: &[u8]| decode_to_vec(data).map(|r| r.1);
let encode_c = |data: &[u8], size| qoi_encode(data, size, 1, channels as _);
let decode_c = |data: &[u8]| qoi_decode(data, channels as _).map(|r| r.1);
check_roundtrip("qoi-rust -> qoi-rust", &img, channels as _, encode, decode);
check_roundtrip("qoi-rust -> qoi.h", &img, channels as _, encode, decode_c);
check_roundtrip("qoi.h -> qoi-rust", &img, channels as _, encode_c, decode);
let size = (img.len() / channels) as u32;
let encoded = encode(&img, size).unwrap();
let encoded_c = encode_c(&img, size).unwrap();
cfg_if! {
if #[cfg(feature = "reference")] {
let eq = encoded.as_slice() == encoded_c.as_ref();
assert!(eq, "qoi-rust [reference mode] doesn't match qoi.h");
} else {
let eq = encoded.len() == encoded_c.len();
assert!(eq, "qoi-rust [non-reference mode] length doesn't match qoi.h");
}
}
n_pixels += size;
}
}

6
vendor/qoi/tests/test_misc.rs vendored Normal file
View File

@ -0,0 +1,6 @@
#[test]
fn test_new_encoder() {
// this used to fail due to `Bytes` not being `pub`
let arr = [0u8];
let _ = qoi::Decoder::new(&arr[..]);
}

114
vendor/qoi/tests/test_ref.rs vendored Normal file
View File

@ -0,0 +1,114 @@
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use anyhow::{bail, Result};
use cfg_if::cfg_if;
use walkdir::{DirEntry, WalkDir};
use qoi::{decode_to_vec, encode_to_vec};
fn find_qoi_png_pairs(root: impl AsRef<Path>) -> Vec<(PathBuf, PathBuf)> {
let root = root.as_ref();
let get_ext =
|path: &Path| path.extension().unwrap_or_default().to_string_lossy().to_ascii_lowercase();
let check_qoi_png_pair = |path: &Path| {
let (qoi, png) = (path.to_path_buf(), path.with_extension("png"));
if qoi.is_file() && get_ext(&qoi) == "qoi" && png.is_file() {
Some((qoi, png))
} else {
None
}
};
let mut out = vec![];
if let Some(pair) = check_qoi_png_pair(root) {
out.push(pair);
} else if root.is_dir() {
out.extend(
WalkDir::new(root)
.follow_links(true)
.into_iter()
.filter_map(Result::ok)
.map(DirEntry::into_path)
.filter_map(|p| check_qoi_png_pair(&p)),
)
}
out
}
struct Image {
pub width: u32,
pub height: u32,
pub channels: u8,
pub data: Vec<u8>,
}
impl Image {
fn from_png(filename: &Path) -> Result<Self> {
let decoder = png::Decoder::new(File::open(filename)?);
let mut reader = decoder.read_info()?;
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf)?;
let bytes = &buf[..info.buffer_size()];
Ok(Self {
width: info.width,
height: info.height,
channels: info.color_type.samples() as u8,
data: bytes.to_vec(),
})
}
}
fn compare_slices(name: &str, desc: &str, result: &[u8], expected: &[u8]) -> Result<()> {
if result == expected {
Ok(())
} else {
if let Some(i) =
(0..result.len().min(expected.len())).position(|i| result[i] != expected[i])
{
bail!(
"{}: {} mismatch at byte {}: expected {:?}, got {:?}",
name,
desc,
i,
&expected[i..(i + 4).min(expected.len())],
&result[i..(i + 4).min(result.len())],
);
} else {
bail!(
"{}: {} length mismatch: expected {}, got {}",
name,
desc,
expected.len(),
result.len()
);
}
}
}
#[test]
fn test_reference_images() -> Result<()> {
let pairs = find_qoi_png_pairs("assets");
assert!(!pairs.is_empty());
for (qoi_path, png_path) in &pairs {
let png_name = png_path.file_name().unwrap_or_default().to_string_lossy();
let img = Image::from_png(png_path)?;
println!("{} {} {} {}", png_name, img.width, img.height, img.channels);
let encoded = encode_to_vec(&img.data, img.width, img.height)?;
let expected = fs::read(qoi_path)?;
assert_eq!(encoded.len(), expected.len()); // this should match regardless
cfg_if! {
if #[cfg(feature = "reference")] {
compare_slices(&png_name, "encoding", &encoded, &expected)?;
}
}
let (_header1, decoded1) = decode_to_vec(&encoded)?;
let (_header2, decoded2) = decode_to_vec(&expected)?;
compare_slices(&png_name, "decoding [1]", &decoded1, &img.data)?;
compare_slices(&png_name, "decoding [2]", &decoded2, &img.data)?;
}
Ok(())
}