Compare commits
361 Commits
Author | SHA1 | Date | |
---|---|---|---|
8d5b619884 | |||
2cbda6e7be | |||
029df21423 | |||
28b92b0f4c | |||
c966fc6954 | |||
d0761bf60e | |||
0f41ee6a2e | |||
0aeafec019 | |||
9a0df4f577 | |||
61714836bb | |||
cff0c100ec | |||
010c49d100 | |||
ff5dbaffc0 | |||
b56d581e4b | |||
aa661aaaa7 | |||
cc3f430bab | |||
139db5b03b | |||
7d0e5c80bd | |||
f716ad9dd1 | |||
671772a20e | |||
b0173f6d68 | |||
3da5872df8 | |||
3deb6e8469 | |||
0c674e0137 | |||
7948d820c3 | |||
5aac3422aa | |||
e8eaa7a232 | |||
04b3382029 | |||
7fe6df5889 | |||
2647c85323 | |||
30d15d79b7 | |||
aeb22ad898 | |||
56ee13c628 | |||
e28e20ea83 | |||
827a7e4418 | |||
bc8242a28d | |||
99159e0bb2 | |||
35054beeb9 | |||
d53fe6c515 | |||
abde91410e | |||
9c487be4fe | |||
6e949186e6 | |||
2f9f92f133 | |||
26130106d9 | |||
8d35d93cdc | |||
dcfec1a44d | |||
4b16be3941 | |||
e020e4db76 | |||
d23d8f3fea | |||
52d9ad5024 | |||
351450e00d | |||
dbd68f185f | |||
41e8f84bf6 | |||
855cd4c61e | |||
d61e911777 | |||
972a28c955 | |||
7e3b202f9e | |||
4231ace045 | |||
8785fe9f00 | |||
a06ba04fa4 | |||
19b3b4087b | |||
4adab9d4e0 | |||
9ab99731f8 | |||
1527109c28 | |||
f65b365318 | |||
706ed3eb68 | |||
c6219a5696 | |||
ef61984291 | |||
36190eb07d | |||
00dfdc8b5c | |||
dfc056e813 | |||
76e3789073 | |||
1ae0c19492 | |||
f6dffaf1e8 | |||
040f8a15e5 | |||
3dfa66d8fd | |||
013c745284 | |||
0d8d07971b | |||
6d5a7ab2fd | |||
514c259711 | |||
c0a27f808f | |||
dc4b97471a | |||
071f0ce957 | |||
d70233b2c6 | |||
c3dcfe780b | |||
660839cb2c | |||
5c787e4173 | |||
b8d77df1e8 | |||
3a0560cb77 | |||
cf90aa165c | |||
07c6d6f7d7 | |||
b40d1edbae | |||
63acdd2796 | |||
7039fcd60c | |||
21c840a2e7 | |||
23c790ce59 | |||
425dfe1221 | |||
43240dbc20 | |||
aca9552827 | |||
e4892c7aa2 | |||
02a49e5410 | |||
f177c193e0 | |||
76583cc18d | |||
948a53e04c | |||
a488d3be0f | |||
bcb2fb5e48 | |||
a4e7750d68 | |||
a2cfc864a2 | |||
b64209287f | |||
d6e88eb1be | |||
370be5c080 | |||
b6bf448c41 | |||
32ac6c3c0f | |||
3b0a7ebc5f | |||
583cdd311e | |||
376d39cc67 | |||
edb8d7b114 | |||
948a86f507 | |||
ed5c0287e6 | |||
ef929be770 | |||
11673fc39c | |||
9434e96f9b | |||
1995afbb82 | |||
14c22321ff | |||
ff148ed284 | |||
fc4bec9099 | |||
1f4e585898 | |||
c1c46c8a76 | |||
b4fab6fbc3 | |||
553eae423a | |||
bce1a069a8 | |||
6005c73e0a | |||
57628d9d4d | |||
6ba3c1b42e | |||
fa70cfc6d2 | |||
d1875b5ac8 | |||
911ca7b65f | |||
5204343519 | |||
06d032339b | |||
9374bd61ae | |||
b9d1e9c3eb | |||
75e7f308a4 | |||
a4f498b23c | |||
805953b1c7 | |||
2e58276f20 | |||
6d7eadd28c | |||
70894be9aa | |||
8308c4c107 | |||
339e11e2fd | |||
3a90672872 | |||
477a589907 | |||
682273b101 | |||
e3612650c0 | |||
028c75fd26 | |||
cc97aaed08 | |||
e42b0b3022 | |||
59e0575c49 | |||
1f8d2b752d | |||
bf50a4253a | |||
d3e2aa8b20 | |||
1181570cdf | |||
4e8e5b6a70 | |||
c861c4b825 | |||
1296ad8179 | |||
773915aefd | |||
a5075d1b6f | |||
7506300a3e | |||
fc0eef8e54 | |||
56be991260 | |||
09d144f892 | |||
009b481b07 | |||
8297ace59d | |||
375f3f02b4 | |||
9a4a7ce5e8 | |||
3d77784bd5 | |||
777b68ab2f | |||
6170f9125d | |||
27e433ef92 | |||
73f04c2ef9 | |||
606bf77678 | |||
2cfe3f58fa | |||
be96074eb0 | |||
6cfd82d35e | |||
7094132132 | |||
6c0831f91f | |||
e6195209d8 | |||
e8a3f40993 | |||
efd968caba | |||
ee79afbe8b | |||
0db7d65c83 | |||
98d5f5187e | |||
e367cb19df | |||
98e8a0237c | |||
fd6f6463ef | |||
07116e8b89 | |||
6804a745e4 | |||
a27f8ed459 | |||
0219479867 | |||
ef826267cc | |||
2870b54937 | |||
943d2b637b | |||
a567e5d18e | |||
1a38a0162f | |||
1f19724a14 | |||
82e6cc8ffc | |||
501cebce7c | |||
f27dbdb94f | |||
729c577ca3 | |||
7e5ecc8091 | |||
a46f5537c8 | |||
6a1595aca2 | |||
a4ea2819c4 | |||
cf918138cf | |||
48375ec75a | |||
9ad3e1d7b4 | |||
05bf89291b | |||
a810fc0762 | |||
a3d43a850a | |||
96bfc2dbeb | |||
00e3421744 | |||
8a27827c71 | |||
aa1e345cd9 | |||
9d7977febf | |||
03d6e36d5d | |||
b3f738a204 | |||
446d5e7008 | |||
2fff023912 | |||
c3002a4d70 | |||
da1070a234 | |||
b060b961e8 | |||
c04a975e00 | |||
61306d7ecd | |||
69b6085d87 | |||
fd0d0a33ce | |||
f9954f5b4b | |||
01af438e9a | |||
193862433a | |||
c2c01cf5f6 | |||
bdcaaa1fb9 | |||
9f38cffd96 | |||
f0d532c2f1 | |||
19e118d78e | |||
56c2272dbe | |||
be12bf0b50 | |||
c69cc218e5 | |||
b53930ad2a | |||
26365fe23c | |||
a8d44375ff | |||
a53f656538 | |||
51cd6a56f8 | |||
82aa277606 | |||
45bc32524a | |||
9c720cc682 | |||
b58a0a28c0 | |||
54e77bf164 | |||
17dffb408c | |||
44fe081388 | |||
0d2e27d3ea | |||
4d20da3282 | |||
d006202752 | |||
52051a310f | |||
63f43a9fc1 | |||
c194b955d8 | |||
63095126f8 | |||
97e1c1f0e1 | |||
e0e21e92fb | |||
3cffa33c45 | |||
9c77051f83 | |||
31f6fd3ca5 | |||
71ff2ac961 | |||
438c1e918f | |||
4192cd1351 | |||
f752c1a978 | |||
013cfa1ecd | |||
0f83363f45 | |||
e276f58931 | |||
b9a9378223 | |||
4bc071df78 | |||
d8201aa77e | |||
9dee61246f | |||
aefa0f7a25 | |||
4ca3d3ae42 | |||
3a62cabad2 | |||
bf6951036d | |||
2f255c7aff | |||
11dbe1e6aa | |||
ae07396158 | |||
0112e3d555 | |||
296f0ef840 | |||
85078d89d6 | |||
91cc726583 | |||
525f32cefe | |||
873cba791d | |||
3973c549dc | |||
5983658ad4 | |||
b743409e06 | |||
2aaba8da96 | |||
6a95206e35 | |||
99fa97792f | |||
8c77fad340 | |||
2ee2169e02 | |||
ba5c1711c7 | |||
e76f25a606 | |||
199362ed1d | |||
6310d49ee8 | |||
c2edcd3d7a | |||
0ad304d761 | |||
075ab8fe42 | |||
03c7ab14d4 | |||
d9518a9426 | |||
8f9c24a5e9 | |||
f49dcc074d | |||
a19e0810e6 | |||
473e467e7b | |||
2103168519 | |||
6a73cc65c5 | |||
92f7ebd3f8 | |||
6c83cf2e0c | |||
947941fbd0 | |||
d6b1ec673a | |||
eb29269432 | |||
28954f7a9a | |||
66d12eb078 | |||
f45f47c9f0 | |||
259a3a36a0 | |||
ce32dfed6e | |||
5039ebd678 | |||
f6f05835c5 | |||
8054316d78 | |||
bd7d5c07bb | |||
03606a0be7 | |||
2392a3423c | |||
cbb62ea555 | |||
e9069e11a4 | |||
fda5167d76 | |||
a79d03c26b | |||
94974653c1 | |||
9dd60534e4 | |||
80356a5aaa | |||
4f8f59d53e | |||
f0a38c19e5 | |||
bdfda4a5f6 | |||
8ebd4e7b6d | |||
ef9ce10bc1 | |||
7053672d3a | |||
be8d23c574 | |||
5506399e0d | |||
ff542c2ae6 | |||
697abf6696 | |||
a902b57ede | |||
ee66591452 | |||
344ba65a57 | |||
7b1567cc9d | |||
dd0b04b319 | |||
30f8a39ec8 | |||
81b438cb56 | |||
c03edb2f26 | |||
324c2243b2 | |||
e969322d00 | |||
de17b3c2c1 | |||
19dc63cf17 |
@ -2,3 +2,7 @@
|
||||
|
||||

|
||||
|
||||
## Highly experimental solanaceae client with Tox built-in
|
||||
|
||||

|
||||
|
||||
|
1
external/CMakeLists.txt
vendored
1
external/CMakeLists.txt
vendored
@ -17,4 +17,5 @@ add_subdirectory(./imgui)
|
||||
|
||||
add_subdirectory(./stb)
|
||||
add_subdirectory(./libwebp)
|
||||
add_subdirectory(./qoi)
|
||||
|
||||
|
11
external/qoi/CMakeLists.txt
vendored
Normal file
11
external/qoi/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR)
|
||||
|
||||
project(qoi C CXX)
|
||||
|
||||
add_library(qoi_interface INTERFACE)
|
||||
target_include_directories(qoi_interface SYSTEM INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
# static lib with impl
|
||||
add_library(qoi "qoi.cpp")
|
||||
target_link_libraries(qoi qoi_interface)
|
||||
|
3
external/qoi/qoi.cpp
vendored
Normal file
3
external/qoi/qoi.cpp
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#define QOI_IMPLEMENTATION
|
||||
#include <qoi/qoi.h>
|
||||
|
5
external/qoi/qoi/.gitignore
vendored
Normal file
5
external/qoi/qoi/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
images/
|
||||
stb_image.h
|
||||
stb_image_write.h
|
||||
qoibench
|
||||
qoiconv
|
21
external/qoi/qoi/LICENSE
vendored
Normal file
21
external/qoi/qoi/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Dominic Szablewski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
22
external/qoi/qoi/Makefile
vendored
Normal file
22
external/qoi/qoi/Makefile
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
CC ?= gcc
|
||||
CFLAGS_BENCH ?= -std=gnu99 -O3
|
||||
LFLAGS_BENCH ?= -lpng
|
||||
CFLAGS_CONV ?= -std=c99 -O3
|
||||
|
||||
TARGET_BENCH ?= qoibench
|
||||
TARGET_CONV ?= qoiconv
|
||||
|
||||
all: $(TARGET_BENCH) $(TARGET_CONV)
|
||||
|
||||
bench: $(TARGET_BENCH)
|
||||
|
||||
$(TARGET_BENCH):$(TARGET_BENCH).c
|
||||
$(CC) $(CFLAGS_BENCH) $(CFLAGS) $(TARGET_BENCH).c -o $(TARGET_BENCH) $(LFLAGS_BENCH)
|
||||
|
||||
conv: $(TARGET_CONV)
|
||||
$(TARGET_CONV):$(TARGET_CONV).c
|
||||
$(CC) $(CFLAGS_CONV) $(CFLAGS) $(TARGET_CONV).c -o $(TARGET_CONV)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(RM) $(TARGET_BENCH) $(TARGET_CONV)
|
179
external/qoi/qoi/README.md
vendored
Normal file
179
external/qoi/qoi/README.md
vendored
Normal file
@ -0,0 +1,179 @@
|
||||

|
||||
|
||||
# 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
|
||||
|
||||
|
||||
## MIME Type, File Extension
|
||||
|
||||
The recommended MIME type for QOI images is `image/qoi`. While QOI is not yet
|
||||
officially registered with IANA, I believe QOI has found enough adoption to
|
||||
prevent any future image format from choosing the same name, thus making a
|
||||
MIME type collision highly unlikely ([see #167](https://github.com/phoboslab/qoi/issues/167)).
|
||||
|
||||
The recommended file extension for QOI images is `.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.
|
||||
|
||||
|
||||
## Improvements, New Versions and Contributing
|
||||
|
||||
The QOI format has been finalized. It was a conscious decision to **not** have a
|
||||
version number in the file header. If you have a working QOI implementation today,
|
||||
you can rest assured that it will be compatible with all QOI files tomorrow.
|
||||
|
||||
There are a lot of interesting ideas for a successor of QOI, but none of these will
|
||||
be implemented here. That doesn't mean you shouldn't experiment with QOI, but please
|
||||
be aware that pull requests that change the format will not be accepted.
|
||||
|
||||
Likewise, pull requests for performance improvements will probably not be accepted
|
||||
either, as this "reference implementation" tries to be as easy to read as possible.
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
- [floooh/qoiview](https://github.com/floooh/qoiview) - native QOI viewer
|
||||
- [pfusik/qoi-fu](https://github.com/pfusik/qoi-fu/releases) - QOI Plugin installer for Windows Explorer, Finder, GNOME, GIMP, Paint.NET and XnView
|
||||
- [iOrange/QoiFileTypeNet](https://github.com/iOrange/QoiFileTypeNet/releases) - QOI Plugin for Paint.NET
|
||||
- [iOrange/QOIThumbnailProvider](https://github.com/iOrange/QOIThumbnailProvider) - Add thumbnails for QOI images in Windows Explorer
|
||||
- [Tom94/tev](https://github.com/Tom94/tev) - another native QOI viewer (allows pixel peeping and comparison with other image formats)
|
||||
- [qoiconverterx](https://apps.apple.com/br/app/qoiconverterx/id1602159820) QOI <=> PNG converter available on the Mac App Store
|
||||
- [kaetemi/qoi-ma](https://github.com/kaetemi/qoi-max) - QOI Bitmap I/O Plugin for 3ds Max
|
||||
- [rtexviewer](https://raylibtech.itch.io/rtexviewer) - texture viewer, supports QOI
|
||||
- [rtexpacker](https://raylibtech.itch.io/rtexpacker) - texture packer, supports QOI
|
||||
- [DmitriySalnikov/godot_qoi](https://github.com/DmitriySalnikov/godot_qoi) - QOI GDNative Addon for Godot Engine
|
||||
- [dan9er/farbfeld-convert-qoi](https://gitlab.com/dan9er/farbfeld-convert-qoi) - QOI <=> farbfeld converter
|
||||
- [LTMX/Unity.QOI](https://github.com/LTMX/Unity.QOI) - QOI Importer and Exporter for the Unity3D Game Engine
|
||||
- [Ben1138/unity-qoi](https://github.com/Ben1138/unity-qoi) - QOI Importer(only) support for the Unity3D Game Engine
|
||||
- [xiaozhuai/jetbrains-qo](https://github.com/xiaozhuai/jetbrains-qoi) - [QOI Support](https://plugins.jetbrains.com/plugin/19352-qoi-support) for Jetbrains' IDE.
|
||||
- [serge-ivamov/QOIql](https://github.com/serge-ivamov/QOIql) - MacOS QuickLook plugin for QOI
|
||||
- [tobozo/kde-thumbnailer-qoi](https://github.com/tobozo/kde-thumbnailer-qoi) - QOI Thumbnailer for KDE
|
||||
- [walksanatora/qoi-thumbnailer-nemo](https://github.com/walksanatora/qoi-thumbnailer-nemo) - QOI Thumbnailer for Nemo
|
||||
- [hzeller/timg](https://github.com/hzeller/timg) - a terminal image viewer with QOI support
|
||||
- [LuisAlfredo92/Super-QOI-converter](https://github.com/LuisAlfredo92/Super-QOI-converter "LuisAlfredo92/Super-QOI-converter") - A program to convert JPG, JPEG, BMP, and PNG to QOI
|
||||
- [Console version](https://github.com/LuisAlfredo92/Super-QOI-converter-Console- "Console version"): Available for Linux, OSX and Windows
|
||||
- [GUI version](https://github.com/LuisAlfredo92/Super-QOI-converter-GUI- "GUI version"): Available only for windows
|
||||
- [tacent view](https://github.com/bluescan/tacentview) - Image and texture viewer, supports QOI
|
||||
- [colemanrgb/qoi2spr](https://github.com/colemanrgb/qoi2spr) - A variety of applications for decoding and encoding of QOI images on [RISC OS](https://www.riscosopen.org/)
|
||||
|
||||
## Implementations & Bindings of QOI
|
||||
|
||||
- [pfusik/qoi-fu](https://github.com/pfusik/qoi-fu) - Fusion, transpiling to C, C++, C#, D, Java, JavaScript, Python, Swift and TypeScript
|
||||
- [kodonnell/qoi](https://github.com/kodonnell/qoi) - Python
|
||||
- [JaffaKetchup/dqoi](https://github.com/JaffaKetchup/dqoi) - Dart, with Flutter support
|
||||
- [Cr4xy/lua-qoi](https://github.com/Cr4xy/lua-qoi) - Lua
|
||||
- [superzazu/SDL_QOI](https://github.com/superzazu/SDL_QOI) - C, SDL2 bindings
|
||||
- [saharNooby/qoi-java](https://github.com/saharNooby/qoi-java) - Java
|
||||
- [MasterQ32/zig-qoi](https://github.com/MasterQ32/zig-qoi) - Zig
|
||||
- [rbino/qoix](https://github.com/rbino/qoix) - Elixir
|
||||
- [NUlliiON/QoiSharp](https://github.com/NUlliiON/QoiSharp) - C#
|
||||
- [aldanor/qoi-rust](https://github.com/aldanor/qoi-rust) - Rust
|
||||
- [zakarumych/rapid-qoi](https://github.com/zakarumych/rapid-qoi) - Rust
|
||||
- [takeyourhatoff/qoi](https://github.com/takeyourhatoff/qoi) - Go
|
||||
- [DosWorld/pasqoi](https://github.com/DosWorld/pasqoi) - Pascal
|
||||
- [elihwyma/Swift-QOI](https://github.com/elihwyma/Swift-QOI) - Swift
|
||||
- [xfmoulet/qoi](https://github.com/xfmoulet/qoi) - Go
|
||||
- [erratique.ch/qoic](https://erratique.ch/software/qoic) - OCaml
|
||||
- [arian/go-qoi](https://github.com/arian/go-qoi) - Go
|
||||
- [kchapelier/qoijs](https://github.com/kchapelier/qoijs) - JavaScript
|
||||
- [KristofferC/QOI.jl](https://github.com/KristofferC/QOI.jl) - Julia
|
||||
- [shadowMitia/libqoi](https://github.com/shadowMitia/libqoi) - C++
|
||||
- [MKCG/php-qoi](https://github.com/MKCG/php-qoi) - PHP
|
||||
- [LightHouseSoftware/qoiformats](https://github.com/LightHouseSoftware/qoiformats) - D
|
||||
- [mhoward540/qoi-nim](https://github.com/mhoward540/qoi-nim) - Nim
|
||||
- [wx257osn2/qoixx](https://github.com/wx257osn2/qoixx) - C++
|
||||
- [Tiefseetauchner/lr-paint](https://github.com/Tiefseetauchner/lr-paint) - Processing
|
||||
- [amstan/qoi-fpga](https://github.com/amstan/qoi-fpga) - FPGA: verilog
|
||||
- [musabkilic/qoi-decoder](https://github.com/musabkilic/qoi-decoder) - Python
|
||||
- [mathpn/py-qoi](https://github.com/mathpn/py-qoi) - Python
|
||||
- [JohannesFriedrich/qoi4R](https://github.com/JohannesFriedrich/qoi4R) - R
|
||||
- [shraiwi/mini-qoi](https://github.com/shraiwi/mini-qoi) - C, streaming decoder
|
||||
- [10maurycy10/libqoi/](https://github.com/10maurycy10/libqoi/) - Rust
|
||||
- [0xd34df00d/hsqoi](https://github.com/0xd34df00d/hsqoi) - Haskell
|
||||
- [418Coffee/qoi-v](https://github.com/418Coffee/qoi-v) - V
|
||||
- [Imagine-Programming/QoiImagePlugin](https://github.com/Imagine-Programming/QoiImagePlugin) - PureBasic
|
||||
- [Fabien-Chouteau/qoi-spark](https://github.com/Fabien-Chouteau/qoi-spark) - Ada/SPARK formally proven
|
||||
- [mzgreen/qoi-kotlin](https://github.com/mzgreen/qoi-kotlin) - Kotlin Multiplatform
|
||||
- [Aftersol/Simplified-QOI-Codec](https://github.com/Aftersol/Simplified-QOI-Codec) - C99, encoder and decoder, freestanding
|
||||
- [AuburnSounds/gamut](https://github.com/AuburnSounds/gamut) - D
|
||||
- [AngusJohnson/TQoiImage](https://github.com/AngusJohnson/TQoiImage) - Delphi
|
||||
- [MarkJeronimus/qoi-java-spi](https://github.com/MarkJeronimus/qoi-java-spi) - Java SPI
|
||||
- [aumouvantsillage/qoi-racket](https://github.com/aumouvantsillage/qoi-racket) - Racket
|
||||
- [rubikscraft/qoi-stream](https://github.com/rubikscraft/qoi-stream) - C99, one byte at a time streaming encoder and decoder
|
||||
- [rubikscraft/qoi-img](https://github.com/rubikscraft/qoi-img) - NodeJS typescript, bindings to both [QOIxx](https://github.com/wx257osn2/qoixx) and [qoi-stream](https://github.com/rubikscraft/qoi-stream)
|
||||
- [grego/hare-qoi](https://git.sr.ht/~grego/hare-qoi) - Hare
|
||||
- [MrNocole/ZTQOI](https://github.com/MrNocole/ZTQOI) - Objective-C
|
||||
- [bpanthi977/qoi](https://github.com/bpanthi977/qoi) - Common Lisp
|
||||
- [Floessie/pam2qoi](https://github.com/Floessie/pam2qoi) - C++
|
||||
- [SpeckyYT/spwn-qoi](https://github.com/SpeckyYT/spwn-qoi) - SPWN
|
||||
- [n00bmind/qoi](https://github.com/n00bmind/qoi) - Jai
|
||||
- [SixLabors/ImageSharp](https://github.com/SixLabors/ImageSharp) - C# image proccesing library
|
||||
- [zertovitch/gid](https://github.com/zertovitch/gid) - Ada
|
||||
- [nazrin/lil](https://codeberg.org/nazrin/lil) - Lua image library
|
||||
|
||||
## QOI Support in Other Software
|
||||
|
||||
- [Amiga OS QOI datatype](https://github.com/dgaw/qoi-datatype) - adds support for decoding QOI images to the Amiga operating system.
|
||||
- [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
|
||||
- [SAIL](https://sail.software) - image decoding library, supports decoding and encoding QOI images
|
||||
- [Orx](https://github.com/orx/orx) - 2D game engine, supports QOI natively
|
||||
- [IrfanView](https://www.irfanview.com) - supports decoding and encoding QOI through its Formats plugin
|
||||
- [ImageMagick](https://github.com/ImageMagick/ImageMagick) - supports decoding and encoding QOI, since 7.1.0-20
|
||||
- [barebox](https://barebox.org) - bootloader, supports decoding QOI images for splash logo, since v2022.03.0
|
||||
- [KorGE](https://korge.org) - & KorIM Kotlin 2D game engine and imaging library, supports decoding and encoding QOI natively since 2.7.0
|
||||
- [DOjS](https://github.com/SuperIlu/DOjS) - DOS JavaScript Canvas implementation supports loading QOI files
|
||||
- [XnView MP](https://www.xnview.com/en/xnviewmp/) - supports decoding QOI since 1.00
|
||||
- [ffmpeg](https://ffmpeg.org/) - supports decoding and encoding QOI since 5.1
|
||||
- [JPEGView](https://github.com/sylikc/jpegview) - lightweight Windows image viewer, supports decoding and encoding of QOI natively, since 1.1.44
|
||||
- [darktable](https://github.com/darktable-org/darktable) - photography workflow application and raw developer, supports decoding since 4.4.0
|
||||
- [KDE](https://kde.org) - supports decoding and encoding QOI images. Implemented in [KImageFormats](https://invent.kde.org/frameworks/kimageformats)
|
||||
- [EFL](https://www.enlightenment.org) - supports decoding and encoding QOI images since 1.27.
|
||||
- [Swingland](https://git.sr.ht/~phlash/swingland) - supports QOI decoding/loading via the `ImageIO` API of this Java Swing reimplemenation for Wayland
|
||||
- [Imagine](https://www.nyam.pe.kr/dev/imagine/) - supports decoding and encoding QOI images since 1.3.9
|
||||
- [Uiua](https://uiua.org) - supports decoding and encoding QOI images since 0.8.0
|
||||
|
||||
## Packages
|
||||
|
||||
- [AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages.
|
||||
- [Debian](https://packages.debian.org/bookworm/source/qoi) - packages for binaries and qoi.h
|
||||
- [Ubuntu](https://launchpad.net/ubuntu/+source/qoi) - packages for binaries and qoi.h
|
||||
|
||||
Packages for other systems [tracked at Repology](https://repology.org/project/qoi/versions).
|
649
external/qoi/qoi/qoi.h
vendored
Normal file
649
external/qoi/qoi/qoi.h
vendored
Normal file
@ -0,0 +1,649 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
QOI - The "Quite OK Image" format for fast, lossless image compression
|
||||
|
||||
-- About
|
||||
|
||||
QOI encodes and decodes images in a lossless format. Compared to stb_image and
|
||||
stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
|
||||
20% better compression.
|
||||
|
||||
|
||||
-- Synopsis
|
||||
|
||||
// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
|
||||
// library to create the implementation.
|
||||
|
||||
#define QOI_IMPLEMENTATION
|
||||
#include "qoi.h"
|
||||
|
||||
// Encode and store an RGBA buffer to the file system. The qoi_desc describes
|
||||
// the input pixel data.
|
||||
qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
|
||||
.width = 1920,
|
||||
.height = 1080,
|
||||
.channels = 4,
|
||||
.colorspace = QOI_SRGB
|
||||
});
|
||||
|
||||
// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
|
||||
// The qoi_desc struct will be filled with the width, height, number of channels
|
||||
// and colorspace read from the file header.
|
||||
qoi_desc desc;
|
||||
void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
|
||||
|
||||
|
||||
|
||||
-- Documentation
|
||||
|
||||
This library provides the following functions;
|
||||
- qoi_read -- read and decode a QOI file
|
||||
- qoi_decode -- decode the raw bytes of a QOI image from memory
|
||||
- qoi_write -- encode and write a QOI file
|
||||
- qoi_encode -- encode an rgba buffer into a QOI image in memory
|
||||
|
||||
See the function declaration below for the signature and more information.
|
||||
|
||||
If you don't want/need the qoi_read and qoi_write functions, you can define
|
||||
QOI_NO_STDIO before including this library.
|
||||
|
||||
This library uses malloc() and free(). To supply your own malloc implementation
|
||||
you can define QOI_MALLOC and QOI_FREE before including this library.
|
||||
|
||||
This library uses memset() to zero-initialize the index. To supply your own
|
||||
implementation you can define QOI_ZEROARR before including this library.
|
||||
|
||||
|
||||
-- Data Format
|
||||
|
||||
A QOI file has a 14 byte header, followed by any number of data "chunks" and an
|
||||
8-byte end marker.
|
||||
|
||||
struct qoi_header_t {
|
||||
char magic[4]; // magic bytes "qoif"
|
||||
uint32_t width; // image width in pixels (BE)
|
||||
uint32_t height; // image height in pixels (BE)
|
||||
uint8_t channels; // 3 = RGB, 4 = RGBA
|
||||
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
};
|
||||
|
||||
Images are encoded row by row, left to right, top to bottom. The decoder and
|
||||
encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
|
||||
image is complete when all pixels specified by width * height have been covered.
|
||||
|
||||
Pixels are encoded as
|
||||
- a run of the previous pixel
|
||||
- an index into an array of previously seen pixels
|
||||
- a difference to the previous pixel value in r,g,b
|
||||
- full r,g,b or r,g,b,a values
|
||||
|
||||
The color channels are assumed to not be premultiplied with the alpha channel
|
||||
("un-premultiplied alpha").
|
||||
|
||||
A running array[64] (zero-initialized) of previously seen pixel values is
|
||||
maintained by the encoder and decoder. Each pixel that is seen by the encoder
|
||||
and decoder is put into this array at the position formed by a hash function of
|
||||
the color value. In the encoder, if the pixel value at the index matches the
|
||||
current pixel, this index position is written to the stream as QOI_OP_INDEX.
|
||||
The hash function for the index is:
|
||||
|
||||
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
|
||||
|
||||
Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
|
||||
bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
|
||||
values encoded in these data bits have the most significant bit on the left.
|
||||
|
||||
The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
|
||||
presence of an 8-bit tag first.
|
||||
|
||||
The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
|
||||
|
||||
|
||||
The possible chunks are:
|
||||
|
||||
|
||||
.- QOI_OP_INDEX ----------.
|
||||
| Byte[0] |
|
||||
| 7 6 5 4 3 2 1 0 |
|
||||
|-------+-----------------|
|
||||
| 0 0 | index |
|
||||
`-------------------------`
|
||||
2-bit tag b00
|
||||
6-bit index into the color index array: 0..63
|
||||
|
||||
A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
|
||||
same index. QOI_OP_RUN should be used instead.
|
||||
|
||||
|
||||
.- QOI_OP_DIFF -----------.
|
||||
| Byte[0] |
|
||||
| 7 6 5 4 3 2 1 0 |
|
||||
|-------+-----+-----+-----|
|
||||
| 0 1 | dr | dg | db |
|
||||
`-------------------------`
|
||||
2-bit tag b01
|
||||
2-bit red channel difference from the previous pixel between -2..1
|
||||
2-bit green channel difference from the previous pixel between -2..1
|
||||
2-bit blue channel difference from the previous pixel between -2..1
|
||||
|
||||
The difference to the current channel values are using a wraparound operation,
|
||||
so "1 - 2" will result in 255, while "255 + 1" will result in 0.
|
||||
|
||||
Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
|
||||
0 (b00). 1 is stored as 3 (b11).
|
||||
|
||||
The alpha value remains unchanged from the previous pixel.
|
||||
|
||||
|
||||
.- QOI_OP_LUMA -------------------------------------.
|
||||
| Byte[0] | Byte[1] |
|
||||
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
|
||||
|-------+-----------------+-------------+-----------|
|
||||
| 1 0 | green diff | dr - dg | db - dg |
|
||||
`---------------------------------------------------`
|
||||
2-bit tag b10
|
||||
6-bit green channel difference from the previous pixel -32..31
|
||||
4-bit red channel difference minus green channel difference -8..7
|
||||
4-bit blue channel difference minus green channel difference -8..7
|
||||
|
||||
The green channel is used to indicate the general direction of change and is
|
||||
encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
|
||||
of the green channel difference and are encoded in 4 bits. I.e.:
|
||||
dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
|
||||
db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
|
||||
|
||||
The difference to the current channel values are using a wraparound operation,
|
||||
so "10 - 13" will result in 253, while "250 + 7" will result in 1.
|
||||
|
||||
Values are stored as unsigned integers with a bias of 32 for the green channel
|
||||
and a bias of 8 for the red and blue channel.
|
||||
|
||||
The alpha value remains unchanged from the previous pixel.
|
||||
|
||||
|
||||
.- QOI_OP_RUN ------------.
|
||||
| Byte[0] |
|
||||
| 7 6 5 4 3 2 1 0 |
|
||||
|-------+-----------------|
|
||||
| 1 1 | run |
|
||||
`-------------------------`
|
||||
2-bit tag b11
|
||||
6-bit run-length repeating the previous pixel: 1..62
|
||||
|
||||
The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
|
||||
(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
|
||||
QOI_OP_RGBA tags.
|
||||
|
||||
|
||||
.- QOI_OP_RGB ------------------------------------------.
|
||||
| Byte[0] | Byte[1] | Byte[2] | Byte[3] |
|
||||
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|
||||
|-------------------------+---------+---------+---------|
|
||||
| 1 1 1 1 1 1 1 0 | red | green | blue |
|
||||
`-------------------------------------------------------`
|
||||
8-bit tag b11111110
|
||||
8-bit red channel value
|
||||
8-bit green channel value
|
||||
8-bit blue channel value
|
||||
|
||||
The alpha value remains unchanged from the previous pixel.
|
||||
|
||||
|
||||
.- QOI_OP_RGBA ---------------------------------------------------.
|
||||
| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
|
||||
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|
||||
|-------------------------+---------+---------+---------+---------|
|
||||
| 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
|
||||
`-----------------------------------------------------------------`
|
||||
8-bit tag b11111111
|
||||
8-bit red channel value
|
||||
8-bit green channel value
|
||||
8-bit blue channel value
|
||||
8-bit alpha channel value
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Header - Public functions */
|
||||
|
||||
#ifndef QOI_H
|
||||
#define QOI_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
|
||||
It describes either the input format (for qoi_write and qoi_encode), or is
|
||||
filled with the description read from the file header (for qoi_read and
|
||||
qoi_decode).
|
||||
|
||||
The colorspace in this qoi_desc is an enum where
|
||||
0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
|
||||
1 = all channels are linear
|
||||
You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
|
||||
informative. It will be saved to the file header, but does not affect
|
||||
how chunks are en-/decoded. */
|
||||
|
||||
#define QOI_SRGB 0
|
||||
#define QOI_LINEAR 1
|
||||
|
||||
typedef struct {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned char channels;
|
||||
unsigned char colorspace;
|
||||
} qoi_desc;
|
||||
|
||||
#ifndef QOI_NO_STDIO
|
||||
|
||||
/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
|
||||
system. The qoi_desc struct must be filled with the image width, height,
|
||||
number of channels (3 = RGB, 4 = RGBA) and the colorspace.
|
||||
|
||||
The function returns 0 on failure (invalid parameters, or fopen or malloc
|
||||
failed) or the number of bytes written on success. */
|
||||
|
||||
int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
|
||||
|
||||
|
||||
/* Read and decode a QOI image from the file system. If channels is 0, the
|
||||
number of channels from the file header is used. If channels is 3 or 4 the
|
||||
output format will be forced into this number of channels.
|
||||
|
||||
The function either returns NULL on failure (invalid data, or malloc or fopen
|
||||
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
|
||||
will be filled with the description from the file header.
|
||||
|
||||
The returned pixel data should be free()d after use. */
|
||||
|
||||
void *qoi_read(const char *filename, qoi_desc *desc, int channels);
|
||||
|
||||
#endif /* QOI_NO_STDIO */
|
||||
|
||||
|
||||
/* Encode raw RGB or RGBA pixels into a QOI image in memory.
|
||||
|
||||
The function either returns NULL on failure (invalid parameters or malloc
|
||||
failed) or a pointer to the encoded data on success. On success the out_len
|
||||
is set to the size in bytes of the encoded data.
|
||||
|
||||
The returned qoi data should be free()d after use. */
|
||||
|
||||
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
|
||||
|
||||
|
||||
/* Decode a QOI image from memory.
|
||||
|
||||
The function either returns NULL on failure (invalid parameters or malloc
|
||||
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
|
||||
is filled with the description from the file header.
|
||||
|
||||
The returned pixel data should be free()d after use. */
|
||||
|
||||
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* QOI_H */
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Implementation */
|
||||
|
||||
#ifdef QOI_IMPLEMENTATION
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef QOI_MALLOC
|
||||
#define QOI_MALLOC(sz) malloc(sz)
|
||||
#define QOI_FREE(p) free(p)
|
||||
#endif
|
||||
#ifndef QOI_ZEROARR
|
||||
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
|
||||
#endif
|
||||
|
||||
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
|
||||
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
|
||||
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
|
||||
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
|
||||
#define QOI_OP_RGB 0xfe /* 11111110 */
|
||||
#define QOI_OP_RGBA 0xff /* 11111111 */
|
||||
|
||||
#define QOI_MASK_2 0xc0 /* 11000000 */
|
||||
|
||||
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
|
||||
#define QOI_MAGIC \
|
||||
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
|
||||
((unsigned int)'i') << 8 | ((unsigned int)'f'))
|
||||
#define QOI_HEADER_SIZE 14
|
||||
|
||||
/* 2GB is the max file size that this implementation can safely handle. We guard
|
||||
against anything larger than that, assuming the worst case with 5 bytes per
|
||||
pixel, rounded down to a nice clean value. 400 million pixels ought to be
|
||||
enough for anybody. */
|
||||
#define QOI_PIXELS_MAX ((unsigned int)400000000)
|
||||
|
||||
typedef union {
|
||||
struct { unsigned char r, g, b, a; } rgba;
|
||||
unsigned int v;
|
||||
} qoi_rgba_t;
|
||||
|
||||
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
|
||||
|
||||
static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
|
||||
bytes[(*p)++] = (0xff000000 & v) >> 24;
|
||||
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
|
||||
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
|
||||
bytes[(*p)++] = (0x000000ff & v);
|
||||
}
|
||||
|
||||
static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
|
||||
unsigned int a = bytes[(*p)++];
|
||||
unsigned int b = bytes[(*p)++];
|
||||
unsigned int c = bytes[(*p)++];
|
||||
unsigned int d = bytes[(*p)++];
|
||||
return a << 24 | b << 16 | c << 8 | d;
|
||||
}
|
||||
|
||||
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
|
||||
int i, max_size, p, run;
|
||||
int px_len, px_end, px_pos, channels;
|
||||
unsigned char *bytes;
|
||||
const unsigned char *pixels;
|
||||
qoi_rgba_t index[64];
|
||||
qoi_rgba_t px, px_prev;
|
||||
|
||||
if (
|
||||
data == NULL || out_len == NULL || desc == NULL ||
|
||||
desc->width == 0 || desc->height == 0 ||
|
||||
desc->channels < 3 || desc->channels > 4 ||
|
||||
desc->colorspace > 1 ||
|
||||
desc->height >= QOI_PIXELS_MAX / desc->width
|
||||
) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
max_size =
|
||||
desc->width * desc->height * (desc->channels + 1) +
|
||||
QOI_HEADER_SIZE + sizeof(qoi_padding);
|
||||
|
||||
p = 0;
|
||||
bytes = (unsigned char *) QOI_MALLOC(max_size);
|
||||
if (!bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qoi_write_32(bytes, &p, QOI_MAGIC);
|
||||
qoi_write_32(bytes, &p, desc->width);
|
||||
qoi_write_32(bytes, &p, desc->height);
|
||||
bytes[p++] = desc->channels;
|
||||
bytes[p++] = desc->colorspace;
|
||||
|
||||
|
||||
pixels = (const unsigned char *)data;
|
||||
|
||||
QOI_ZEROARR(index);
|
||||
|
||||
run = 0;
|
||||
px_prev.rgba.r = 0;
|
||||
px_prev.rgba.g = 0;
|
||||
px_prev.rgba.b = 0;
|
||||
px_prev.rgba.a = 255;
|
||||
px = px_prev;
|
||||
|
||||
px_len = desc->width * desc->height * desc->channels;
|
||||
px_end = px_len - desc->channels;
|
||||
channels = desc->channels;
|
||||
|
||||
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
|
||||
px.rgba.r = pixels[px_pos + 0];
|
||||
px.rgba.g = pixels[px_pos + 1];
|
||||
px.rgba.b = pixels[px_pos + 2];
|
||||
|
||||
if (channels == 4) {
|
||||
px.rgba.a = pixels[px_pos + 3];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pixels[px_pos + 0] = px.rgba.r;
|
||||
pixels[px_pos + 1] = px.rgba.g;
|
||||
pixels[px_pos + 2] = px.rgba.b;
|
||||
|
||||
if (channels == 4) {
|
||||
pixels[px_pos + 3] = px.rgba.a;
|
||||
}
|
||||
}
|
||||
|
||||
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, err;
|
||||
void *encoded;
|
||||
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
encoded = qoi_encode(data, desc, &size);
|
||||
if (!encoded) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fwrite(encoded, 1, size, f);
|
||||
fflush(f);
|
||||
err = ferror(f);
|
||||
fclose(f);
|
||||
|
||||
QOI_FREE(encoded);
|
||||
return err ? 0 : 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 || fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data = QOI_MALLOC(size);
|
||||
if (!data) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bytes_read = fread(data, 1, size, f);
|
||||
fclose(f);
|
||||
pixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels);
|
||||
QOI_FREE(data);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
#endif /* QOI_NO_STDIO */
|
||||
#endif /* QOI_IMPLEMENTATION */
|
610
external/qoi/qoi/qoibench.c
vendored
Normal file
610
external/qoi/qoi/qoibench.c
vendored
Normal file
@ -0,0 +1,610 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
#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 %ld 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;
|
||||
|
||||
enum {
|
||||
LIBPNG,
|
||||
STBI,
|
||||
QOI,
|
||||
BENCH_COUNT /* must be the last element */
|
||||
};
|
||||
static const char *const lib_names[BENCH_COUNT] = {
|
||||
// NOTE: pad with spaces so everything lines up properly
|
||||
[LIBPNG] = "libpng: ",
|
||||
[STBI] = "stbi: ",
|
||||
[QOI] = "qoi: ",
|
||||
};
|
||||
|
||||
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 libs[BENCH_COUNT];
|
||||
} benchmark_result_t;
|
||||
|
||||
|
||||
void benchmark_print_result(benchmark_result_t res) {
|
||||
res.px /= res.count;
|
||||
res.raw_size /= res.count;
|
||||
|
||||
double px = res.px;
|
||||
printf(" decode ms encode ms decode mpps encode mpps size kb rate\n");
|
||||
for (int i = 0; i < BENCH_COUNT; ++i) {
|
||||
if (opt_nopng && (i == LIBPNG || i == STBI)) {
|
||||
continue;
|
||||
}
|
||||
res.libs[i].encode_time /= res.count;
|
||||
res.libs[i].decode_time /= res.count;
|
||||
res.libs[i].size /= res.count;
|
||||
printf(
|
||||
"%s %8.1f %8.1f %8.2f %8.2f %8ld %4.1f%%\n",
|
||||
lib_names[i],
|
||||
(double)res.libs[i].decode_time/1000000.0,
|
||||
(double)res.libs[i].encode_time/1000000.0,
|
||||
(res.libs[i].decode_time > 0 ? px / ((double)res.libs[i].decode_time/1000.0) : 0),
|
||||
(res.libs[i].encode_time > 0 ? px / ((double)res.libs[i].encode_time/1000.0) : 0),
|
||||
res.libs[i].size/1024,
|
||||
((double)res.libs[i].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 encoding %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.libs[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.libs[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.libs[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.libs[LIBPNG].encode_time, {
|
||||
int enc_size;
|
||||
void *enc_p = libpng_encode(pixels, w, h, channels, &enc_size);
|
||||
res.libs[LIBPNG].size = enc_size;
|
||||
free(enc_p);
|
||||
});
|
||||
|
||||
BENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[STBI].encode_time, {
|
||||
int enc_size = 0;
|
||||
stbi_write_png_to_func(stbi_write_callback, &enc_size, w, h, channels, pixels, 0);
|
||||
res.libs[STBI].size = enc_size;
|
||||
});
|
||||
}
|
||||
|
||||
BENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[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.libs[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);
|
||||
}
|
||||
|
||||
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;
|
||||
for (int i = 0; i < BENCH_COUNT; ++i) {
|
||||
dir_total.libs[i].encode_time += res.libs[i].encode_time;
|
||||
dir_total.libs[i].decode_time += res.libs[i].decode_time;
|
||||
dir_total.libs[i].size += res.libs[i].size;
|
||||
}
|
||||
|
||||
grand_total->count++;
|
||||
grand_total->raw_size += res.raw_size;
|
||||
grand_total->px += res.px;
|
||||
for (int i = 0; i < BENCH_COUNT; ++i) {
|
||||
grand_total->libs[i].encode_time += res.libs[i].encode_time;
|
||||
grand_total->libs[i].decode_time += res.libs[i].decode_time;
|
||||
grand_total->libs[i].size += res.libs[i].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;
|
||||
}
|
91
external/qoi/qoi/qoiconv.c
vendored
Normal file
91
external/qoi/qoi/qoiconv.c
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
Command line tool to convert between png <> qoi format
|
||||
|
||||
Requires:
|
||||
-"stb_image.h" (https://github.com/nothings/stb/blob/master/stb_image.h)
|
||||
-"stb_image_write.h" (https://github.com/nothings/stb/blob/master/stb_image_write.h)
|
||||
-"qoi.h" (https://github.com/phoboslab/qoi/blob/master/qoi.h)
|
||||
|
||||
Compile with:
|
||||
gcc qoiconv.c -std=c99 -O3 -o qoiconv
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#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;
|
||||
}
|
32
external/qoi/qoi/qoifuzz.c
vendored
Normal file
32
external/qoi/qoi/qoifuzz.c
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
clang fuzzing harness for qoi_decode
|
||||
|
||||
Compile and run with:
|
||||
clang -fsanitize=address,fuzzer -g -O0 qoifuzz.c && ./a.out
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#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;
|
||||
}
|
2
external/solanaceae_contact
vendored
2
external/solanaceae_contact
vendored
Submodule external/solanaceae_contact updated: 2d73c7272c...e40271670b
2
external/solanaceae_message3
vendored
2
external/solanaceae_message3
vendored
Submodule external/solanaceae_message3 updated: 002aa00279...b8893b1c5c
2
external/solanaceae_plugin
vendored
2
external/solanaceae_plugin
vendored
Submodule external/solanaceae_plugin updated: 87b3d15a2b...82cfb6d492
2
external/solanaceae_tox
vendored
2
external/solanaceae_tox
vendored
Submodule external/solanaceae_tox updated: b2a3cb7052...eeb57e137d
2
external/solanaceae_util
vendored
2
external/solanaceae_util
vendored
Submodule external/solanaceae_util updated: db57a7c5e9...d304d719e9
14
external/stb/stb/README.md
vendored
14
external/stb/stb/README.md
vendored
@ -5,16 +5,18 @@ stb
|
||||
|
||||
single-file public domain (or MIT licensed) libraries for C/C++
|
||||
|
||||
# This project discusses security-relevant bugs in public in Github Issues and Pull Requests, and it may take significant time for security fixes to be implemented or merged. If this poses an unreasonable risk to your project, do not use stb libraries.
|
||||
|
||||
Noteworthy:
|
||||
|
||||
* image loader: [stb_image.h](stb_image.h)
|
||||
* image writer: [stb_image_write.h](stb_image_write.h)
|
||||
* image resizer: [stb_image_resize.h](stb_image_resize.h)
|
||||
* image resizer: [stb_image_resize2.h](stb_image_resize2.h)
|
||||
* font text rasterizer: [stb_truetype.h](stb_truetype.h)
|
||||
* typesafe containers: [stb_ds.h](stb_ds.h)
|
||||
|
||||
Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, stb_image_resize
|
||||
by Jorge L. "VinoBS" Rodriguez, and stb_sprintf by Jeff Roberts.
|
||||
Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, original stb_image_resize
|
||||
by Jorge L. "VinoBS" Rodriguez, and stb_image_resize2 and stb_sprintf by Jeff Roberts.
|
||||
|
||||
<a name="stb_libs"></a>
|
||||
|
||||
@ -22,10 +24,10 @@ library | lastest version | category | LoC | description
|
||||
--------------------- | ---- | -------- | --- | --------------------------------
|
||||
**[stb_vorbis.c](stb_vorbis.c)** | 1.22 | audio | 5584 | decode ogg vorbis files from file/memory to float/16-bit signed output
|
||||
**[stb_hexwave.h](stb_hexwave.h)** | 0.5 | audio | 680 | audio waveform synthesizer
|
||||
**[stb_image.h](stb_image.h)** | 2.28 | graphics | 7987 | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC
|
||||
**[stb_image.h](stb_image.h)** | 2.29 | graphics | 7985 | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC
|
||||
**[stb_truetype.h](stb_truetype.h)** | 1.26 | graphics | 5077 | parse, decode, and rasterize characters from truetype fonts
|
||||
**[stb_image_write.h](stb_image_write.h)** | 1.16 | graphics | 1724 | image writing to disk: PNG, TGA, BMP
|
||||
**[stb_image_resize.h](stb_image_resize.h)** | 0.97 | graphics | 2634 | resize images larger/smaller with good quality
|
||||
**[stb_image_resize2.h](stb_image_resize2.h)** | 2.04 | graphics | 10325 | resize images larger/smaller with good quality
|
||||
**[stb_rect_pack.h](stb_rect_pack.h)** | 1.01 | graphics | 623 | simple 2D rectangle packer with decent quality
|
||||
**[stb_perlin.h](stb_perlin.h)** | 0.5 | graphics | 428 | perlin's revised simplex noise w/ different seeds
|
||||
**[stb_ds.h](stb_ds.h)** | 0.67 | utility | 1895 | typesafe dynamic array and hash tables for C, will compile in C++
|
||||
@ -43,7 +45,7 @@ library | lastest version | category | LoC | description
|
||||
**[stb_include.h](stb_include.h)** | 0.02 | misc | 295 | implement recursive #include support, particularly for GLSL
|
||||
|
||||
Total libraries: 21
|
||||
Total lines of C code: 43117
|
||||
Total lines of C code: 50806
|
||||
|
||||
|
||||
FAQ
|
||||
|
3
external/stb/stb/stb_image.h
vendored
3
external/stb/stb/stb_image.h
vendored
@ -1,4 +1,4 @@
|
||||
/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
|
||||
/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb
|
||||
no warranty implied; use at your own risk
|
||||
|
||||
Do this:
|
||||
@ -48,6 +48,7 @@ LICENSE
|
||||
|
||||
RECENT REVISION HISTORY:
|
||||
|
||||
2.29 (2023-05-xx) optimizations
|
||||
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
|
||||
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
|
||||
2.26 (2020-07-13) many minor fixes
|
||||
|
10325
external/stb/stb/stb_image_resize2.h
vendored
Normal file
10325
external/stb/stb/stb_image_resize2.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
224
external/stb/stb/stb_image_resize_test/dotimings.c
vendored
Normal file
224
external/stb/stb/stb_image_resize_test/dotimings.c
vendored
Normal file
@ -0,0 +1,224 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define stop() __debugbreak()
|
||||
#include <windows.h>
|
||||
#define int64 __int64
|
||||
#pragma warning(disable:4127)
|
||||
|
||||
#define get_milliseconds GetTickCount
|
||||
|
||||
#else
|
||||
|
||||
#define stop() __builtin_trap()
|
||||
#define int64 long long
|
||||
|
||||
typedef unsigned int U32;
|
||||
typedef unsigned long long U64;
|
||||
|
||||
#include <time.h>
|
||||
static int get_milliseconds()
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime( CLOCK_MONOTONIC, &ts );
|
||||
return (U32) ( ( ((U64)(U32)ts.tv_sec) * 1000LL ) + (U64)(((U32)ts.tv_nsec+500000)/1000000) );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(TIME_SIMD)
|
||||
// default for most platforms
|
||||
#elif defined(TIME_SCALAR)
|
||||
#define STBIR_NO_SIMD
|
||||
#else
|
||||
#error You must define TIME_SIMD or TIME_SCALAR when compiling this file.
|
||||
#endif
|
||||
|
||||
#define STBIR_PROFILE
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#define STBIR__V_FIRST_INFO_BUFFER v_info
|
||||
#include "stb_image_resize2.h" // new one!
|
||||
|
||||
#if defined(TIME_SIMD) && !defined(STBIR_SIMD)
|
||||
#error Timing SIMD, but scalar was ON!
|
||||
#endif
|
||||
|
||||
#if defined(TIME_SCALAR) && defined(STBIR_SIMD)
|
||||
#error Timing scalar, but SIMD was ON!
|
||||
#endif
|
||||
|
||||
#define HEADER 32
|
||||
|
||||
|
||||
static int file_write( const char *filename, void * buffer, size_t size )
|
||||
{
|
||||
FILE * f = fopen( filename, "wb" );
|
||||
if ( f == 0 ) return 0;
|
||||
if ( fwrite( buffer, 1, size, f) != size ) return 0;
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int64 nresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt )
|
||||
{
|
||||
STBIR_RESIZE resize;
|
||||
int t;
|
||||
int64 b;
|
||||
|
||||
stbir_resize_init( &resize, i, ix, iy, ip, o, ox, oy, op, buf, type );
|
||||
stbir_set_edgemodes( &resize, edg, edg );
|
||||
stbir_set_filters( &resize, flt, flt );
|
||||
|
||||
stbir_build_samplers_with_splits( &resize, 1 );
|
||||
|
||||
b = 0x7fffffffffffffffULL;
|
||||
for( t = 0 ; t < 16 ; t++ )
|
||||
{
|
||||
STBIR_PROFILE_INFO profile;
|
||||
int64 v;
|
||||
if(!stbir_resize_extended( &resize ) )
|
||||
stop();
|
||||
stbir_resize_extended_profile_info( &profile, &resize );
|
||||
v = profile.clocks[1]+profile.clocks[2];
|
||||
if ( v < b )
|
||||
{
|
||||
b = v;
|
||||
t = 0;
|
||||
}
|
||||
}
|
||||
|
||||
stbir_free_samplers( &resize );
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
#define INSIZES 5
|
||||
#define TYPESCOUNT 5
|
||||
#define NUM 64
|
||||
|
||||
static const int sizes[INSIZES]={63,126,252,520,772};
|
||||
static const int types[TYPESCOUNT]={STBIR_1CHANNEL,STBIR_2CHANNEL,STBIR_RGB,STBIR_4CHANNEL,STBIR_RGBA};
|
||||
static const int effective[TYPESCOUNT]={1,2,3,4,7};
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
unsigned char * input;
|
||||
unsigned char * output;
|
||||
int dimensionx, dimensiony;
|
||||
int scalex, scaley;
|
||||
int totalms;
|
||||
int timing_count;
|
||||
int ir;
|
||||
int * file;
|
||||
int * ts;
|
||||
int64 totalcycles;
|
||||
|
||||
if ( argc != 6 )
|
||||
{
|
||||
printf("command: dotimings x_samps y_samps x_scale y_scale outfilename\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
input = malloc( 4*1200*1200 );
|
||||
memset( input, 0x80, 4*1200*1200 );
|
||||
output = malloc( 4*10000*10000ULL );
|
||||
|
||||
dimensionx = atoi( argv[1] );
|
||||
dimensiony = atoi( argv[2] );
|
||||
scalex = atoi( argv[3] );
|
||||
scaley = atoi( argv[4] );
|
||||
|
||||
timing_count = dimensionx * dimensiony * INSIZES * TYPESCOUNT;
|
||||
|
||||
file = malloc( sizeof(int) * ( 2 * timing_count + HEADER ) );
|
||||
ts = file + HEADER;
|
||||
|
||||
totalms = get_milliseconds();
|
||||
totalcycles = STBIR_PROFILE_FUNC();
|
||||
for( ir = 0 ; ir < INSIZES ; ir++ )
|
||||
{
|
||||
int ix, iy, ty;
|
||||
ix = iy = sizes[ir];
|
||||
|
||||
for( ty = 0 ; ty < TYPESCOUNT ; ty++ )
|
||||
{
|
||||
int h, hh;
|
||||
|
||||
h = 1;
|
||||
for( hh = 0 ; hh < dimensiony; hh++ )
|
||||
{
|
||||
int ww, w = 1;
|
||||
for( ww = 0 ; ww < dimensionx; ww++ )
|
||||
{
|
||||
int64 VF, HF;
|
||||
int good;
|
||||
|
||||
v_info.control_v_first = 2; // vertical first
|
||||
VF = nresize( output, w, h, (w*4*1)&~3, input, ix, iy, ix*4*1, types[ty], STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_MITCHELL );
|
||||
v_info.control_v_first = 1; // horizonal first
|
||||
HF = nresize( output, w, h, (w*4*1)&~3, input, ix, iy, ix*4*1, types[ty], STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_MITCHELL );
|
||||
|
||||
good = ( ((HF<=VF) && (!v_info.v_first)) || ((VF<=HF) && (v_info.v_first)));
|
||||
|
||||
// printf("\r%d,%d, %d,%d, %d, %I64d,%I64d, // Good: %c(%c-%d) CompEst: %.1f %.1f\n", ix, iy, w, h, ty, VF, HF, good?'y':'n', v_info.v_first?'v':'h', v_info.v_resize_classification, v_info.v_cost,v_info.h_cost );
|
||||
ts[0] = (int)VF;
|
||||
ts[1] = (int)HF;
|
||||
|
||||
ts += 2;
|
||||
|
||||
w += scalex;
|
||||
}
|
||||
printf(".");
|
||||
h += scaley;
|
||||
}
|
||||
}
|
||||
}
|
||||
totalms = get_milliseconds() - totalms;
|
||||
totalcycles = STBIR_PROFILE_FUNC() - totalcycles;
|
||||
|
||||
printf("\n");
|
||||
|
||||
file[0] = 'VFT1';
|
||||
|
||||
#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ )
|
||||
file[1] = 1; // x64
|
||||
#elif defined( _M_AMD64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) || defined(__ARM_NEON) || defined(__arm__) || defined( _M_ARM )
|
||||
file[1] = 2; // arm
|
||||
#else
|
||||
file[1] = 99; // who knows???
|
||||
#endif
|
||||
|
||||
#ifdef STBIR_SIMD8
|
||||
file[2] = 2; // simd-8
|
||||
#elif defined( STBIR_SIMD )
|
||||
file[2] = 1; // simd-4
|
||||
#else
|
||||
file[2] = 0; // nosimd
|
||||
#endif
|
||||
|
||||
file[3] = dimensionx; // dimx
|
||||
file[4] = dimensiony; // dimy
|
||||
file[5] = TYPESCOUNT; // channel types
|
||||
file[ 6] = types[0]; file[7] = types[1]; file[8] = types[2]; file[9] = types[3]; file[10] = types[4]; // buffer_type
|
||||
file[11] = effective[0]; file[12] = effective[1]; file[13] = effective[2]; file[14] = effective[3]; file[15] = effective[4]; // effective channels
|
||||
file[16] = INSIZES; // resizes
|
||||
file[17] = sizes[0]; file[18] = sizes[0]; // input sizes (w x h)
|
||||
file[19] = sizes[1]; file[20] = sizes[1];
|
||||
file[21] = sizes[2]; file[22] = sizes[2];
|
||||
file[23] = sizes[3]; file[24] = sizes[3];
|
||||
file[25] = sizes[4]; file[26] = sizes[4];
|
||||
file[27] = scalex; file[28] = scaley; // scale the dimx and dimy amount ( for(i=0;i<dimx) outputx = 1 + i*scalex; )
|
||||
file[29] = totalms;
|
||||
((int64*)(file+30))[0] = totalcycles;
|
||||
|
||||
if ( !file_write( argv[5], file, sizeof(int) * ( 2 * timing_count + HEADER ) ) )
|
||||
printf( "Error writing file: %s\n", argv[5] );
|
||||
else
|
||||
printf( "Successfully wrote timing file: %s\n", argv[5] );
|
||||
|
||||
return 0;
|
||||
}
|
2738
external/stb/stb/stb_image_resize_test/old_image_resize.h
vendored
Normal file
2738
external/stb/stb/stb_image_resize_test/old_image_resize.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
56
external/stb/stb/stb_image_resize_test/oldir.c
vendored
Normal file
56
external/stb/stb/stb_image_resize_test/oldir.c
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define stop() __debugbreak()
|
||||
#else
|
||||
#define stop() __builtin_trap()
|
||||
#endif
|
||||
|
||||
//#define HEAVYTM
|
||||
#include "tm.h"
|
||||
|
||||
#define STBIR_SATURATE_INT
|
||||
#define STB_IMAGE_RESIZE_STATIC
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "old_image_resize.h"
|
||||
|
||||
|
||||
static int types[4] = { STBIR_TYPE_UINT8, STBIR_TYPE_UINT8, STBIR_TYPE_UINT16, STBIR_TYPE_FLOAT };
|
||||
static int edges[4] = { STBIR_EDGE_CLAMP, STBIR_EDGE_REFLECT, STBIR_EDGE_ZERO, STBIR_EDGE_WRAP };
|
||||
static int flts[5] = { STBIR_FILTER_BOX, STBIR_FILTER_TRIANGLE, STBIR_FILTER_CUBICBSPLINE, STBIR_FILTER_CATMULLROM, STBIR_FILTER_MITCHELL };
|
||||
static int channels[20] = { 1, 2, 3, 4, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2 };
|
||||
static int alphapos[20] = { -1, -1, -1, -1, 3,0, 1,0, 3,0, 1,0, 3,0, 1,0, 3,0, 1,0 };
|
||||
|
||||
|
||||
void oresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt )
|
||||
{
|
||||
int t = types[type];
|
||||
int ic = channels[buf];
|
||||
int alpha = alphapos[buf];
|
||||
int e = edges[edg];
|
||||
int f = flts[flt];
|
||||
int space = ( type == 1 ) ? STBIR_COLORSPACE_SRGB : 0;
|
||||
int flags = ( buf >= 16 ) ? STBIR_FLAG_ALPHA_PREMULTIPLIED : ( ( buf >= 12 ) ? STBIR_FLAG_ALPHA_OUT_PREMULTIPLIED : ( ( buf >= 8 ) ? (STBIR_FLAG_ALPHA_PREMULTIPLIED|STBIR_FLAG_ALPHA_OUT_PREMULTIPLIED) : 0 ) );
|
||||
stbir_uint64 start;
|
||||
|
||||
ENTER( "Resize (old)" );
|
||||
start = tmGetAccumulationStart( tm_mask );
|
||||
|
||||
if(!stbir_resize( i, ix, iy, ip, o, ox, oy, op, t, ic, alpha, flags, e, e, f, f, space, 0 ) )
|
||||
stop();
|
||||
|
||||
#ifdef STBIR_PROFILE
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.setup, "Setup (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.filters, "Filters (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.looping, "Looping (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.vertical, "Vertical (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.horizontal, "Horizontal (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.decode, "Scanline input (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.encode, "Scanline output (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.alpha, "Alpha weighting (old)" );
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.unalpha, "Alpha unweighting (old)" );
|
||||
#endif
|
||||
|
||||
LEAVE();
|
||||
}
|
992
external/stb/stb/stb_image_resize_test/stbirtest.c
vendored
Normal file
992
external/stb/stb/stb_image_resize_test/stbirtest.c
vendored
Normal file
@ -0,0 +1,992 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//#define HEAVYTM
|
||||
#include "tm.h"
|
||||
|
||||
#ifdef RADUSETM3
|
||||
tm_api * g_tm_api;
|
||||
//#define PROFILE_MODE
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define stop() __debugbreak()
|
||||
#include <windows.h>
|
||||
#define int64 __int64
|
||||
#define uint64 unsigned __int64
|
||||
#else
|
||||
#define stop() __builtin_trap()
|
||||
#define int64 long long
|
||||
#define uint64 unsigned long long
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable:4127)
|
||||
#endif
|
||||
|
||||
//#define NOCOMP
|
||||
|
||||
|
||||
//#define PROFILE_NEW_ONLY
|
||||
//#define PROFILE_MODE
|
||||
|
||||
|
||||
#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ )
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
uint64 __rdtsc();
|
||||
#define __cycles() __rdtsc()
|
||||
|
||||
#else // non msvc
|
||||
|
||||
static inline uint64 __cycles()
|
||||
{
|
||||
unsigned int lo, hi;
|
||||
asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) );
|
||||
return ( ( (uint64) hi ) << 32 ) | ( (uint64) lo );
|
||||
}
|
||||
|
||||
#endif // msvc
|
||||
|
||||
#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define __cycles() _ReadStatusReg(ARM64_CNTVCT)
|
||||
|
||||
#else
|
||||
|
||||
static inline uint64 __cycles()
|
||||
{
|
||||
uint64 tsc;
|
||||
asm volatile("mrs %0, cntvct_el0" : "=r" (tsc));
|
||||
return tsc;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#else // x64, arm
|
||||
|
||||
#error Unknown platform for timing.
|
||||
|
||||
#endif //x64 and
|
||||
|
||||
|
||||
#ifdef PROFILE_MODE
|
||||
|
||||
#define STBIR_ASSERT(cond)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
#undef STBIR_ASSERT
|
||||
#define STBIR_ASSERT(cond) { if (!(cond)) stop(); }
|
||||
#endif
|
||||
|
||||
|
||||
#define SHRINKBYW 2
|
||||
#define ZOOMBYW 2
|
||||
#define SHRINKBYH 2
|
||||
#define ZOOMBYH 2
|
||||
|
||||
|
||||
int mem_count = 0;
|
||||
|
||||
#ifdef TEST_WITH_VALLOC
|
||||
|
||||
#define STBIR__SEPARATE_ALLOCATIONS
|
||||
|
||||
#if TEST_WITH_LIMIT_AT_FRONT
|
||||
|
||||
void * wmalloc(SIZE_T size)
|
||||
{
|
||||
static unsigned int pagesize=0;
|
||||
void* p;
|
||||
SIZE_T s;
|
||||
|
||||
// get the page size, if we haven't yet
|
||||
if (pagesize==0)
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
pagesize=si.dwPageSize;
|
||||
}
|
||||
|
||||
// we need room for the size, 8 bytes to hide the original pointer and a
|
||||
// validation dword, and enough data to completely fill one page
|
||||
s=(size+(pagesize-1))&~(pagesize-1);
|
||||
|
||||
// allocate the size plus a page (for the guard)
|
||||
p=VirtualAlloc(0,(SIZE_T)s,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void wfree(void * ptr)
|
||||
{
|
||||
if (ptr)
|
||||
{
|
||||
if ( ((ptrdiff_t)ptr) & 4095 ) stop();
|
||||
if ( VirtualFree(ptr,0,MEM_RELEASE) == 0 ) stop();
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void * wmalloc(SIZE_T size)
|
||||
{
|
||||
static unsigned int pagesize=0;
|
||||
void* p;
|
||||
SIZE_T s;
|
||||
|
||||
// get the page size, if we haven't yet
|
||||
if (pagesize==0)
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
pagesize=si.dwPageSize;
|
||||
}
|
||||
|
||||
// we need room for the size, 8 bytes to hide the original pointer and a
|
||||
// validation dword, and enough data to completely fill one page
|
||||
s=(size+16+(pagesize-1))&~(pagesize-1);
|
||||
|
||||
// allocate the size plus a page (for the guard)
|
||||
p=VirtualAlloc(0,(SIZE_T)(s+pagesize+pagesize),MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
|
||||
|
||||
if (p)
|
||||
{
|
||||
DWORD oldprot;
|
||||
void* orig=p;
|
||||
|
||||
// protect the first page
|
||||
VirtualProtect(((char*)p),pagesize,PAGE_NOACCESS,&oldprot);
|
||||
|
||||
// protect the final page
|
||||
VirtualProtect(((char*)p)+s+pagesize,pagesize,PAGE_NOACCESS,&oldprot);
|
||||
|
||||
// now move the returned pointer so that it bumps right up against the
|
||||
// the next (protected) page (this may result in unaligned return
|
||||
// addresses - pre-align the sizes if you always want aligned ptrs)
|
||||
//#define ERROR_ON_FRONT
|
||||
#ifdef ERROR_ON_FRONT
|
||||
p=((char*)p)+pagesize+16;
|
||||
#else
|
||||
p=((char*)p)+(s-size)+pagesize;
|
||||
#endif
|
||||
|
||||
// hide the validation value and the original pointer (which we'll
|
||||
// need used for freeing) right behind the returned pointer
|
||||
((unsigned int*)p)[-1]=0x98765432;
|
||||
((void**)p)[-2]=orig;
|
||||
++mem_count;
|
||||
//printf("aloc: %p bytes: %d\n",p,(int)size);
|
||||
return(p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wfree(void * ptr)
|
||||
{
|
||||
if (ptr)
|
||||
{
|
||||
int err=0;
|
||||
|
||||
// is this one of our allocations?
|
||||
if (((((unsigned int*)ptr)[-1])!=0x98765432) || ((((void**)ptr)[-2])==0))
|
||||
{
|
||||
err=1;
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
__debugbreak();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// back up to find the original pointer
|
||||
void* p=((void**)ptr)[-2];
|
||||
|
||||
// clear the validation value and the original pointer
|
||||
((unsigned int*)ptr)[-1]=0;
|
||||
((void**)ptr)[-2]=0;
|
||||
|
||||
//printf("free: %p\n",ptr);
|
||||
|
||||
--mem_count;
|
||||
|
||||
// now free the pages
|
||||
if (p)
|
||||
VirtualFree(p,0,MEM_RELEASE);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#define STBIR_MALLOC(size,user_data) ((void)(user_data), wmalloc(size))
|
||||
#define STBIR_FREE(ptr,user_data) ((void)(user_data), wfree(ptr))
|
||||
|
||||
#endif
|
||||
|
||||
#define STBIR_PROFILE
|
||||
//#define STBIR_NO_SIMD
|
||||
//#define STBIR_AVX
|
||||
//#define STBIR_AVX2
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "stb_image_resize2.h" // new one!
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
|
||||
int tsizes[5] = { 1, 1, 2, 4, 2 };
|
||||
int ttypes[5] = { STBIR_TYPE_UINT8, STBIR_TYPE_UINT8_SRGB, STBIR_TYPE_UINT16, STBIR_TYPE_FLOAT, STBIR_TYPE_HALF_FLOAT };
|
||||
|
||||
int cedges[4] = { STBIR_EDGE_CLAMP, STBIR_EDGE_REFLECT, STBIR_EDGE_ZERO, STBIR_EDGE_WRAP };
|
||||
int flts[5] = { STBIR_FILTER_BOX, STBIR_FILTER_TRIANGLE, STBIR_FILTER_CUBICBSPLINE, STBIR_FILTER_CATMULLROM, STBIR_FILTER_MITCHELL };
|
||||
int buffers[20] = { STBIR_1CHANNEL, STBIR_2CHANNEL, STBIR_RGB, STBIR_4CHANNEL,
|
||||
STBIR_BGRA, STBIR_ARGB, STBIR_RA, STBIR_AR,
|
||||
STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM,
|
||||
STBIR_RGBA, STBIR_ARGB, STBIR_RA, STBIR_AR,
|
||||
STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM,
|
||||
};
|
||||
int obuffers[20] = { STBIR_1CHANNEL, STBIR_2CHANNEL, STBIR_RGB, STBIR_4CHANNEL,
|
||||
STBIR_BGRA, STBIR_ARGB, STBIR_RA, STBIR_AR,
|
||||
STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM,
|
||||
STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM,
|
||||
STBIR_RGBA, STBIR_ARGB, STBIR_RA, STBIR_AR,
|
||||
};
|
||||
|
||||
int bchannels[20] = { 1, 2, 3, 4, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2 };
|
||||
int alphapos[20] = { -1, -1, -1, -1, 3,0, 1,0, 3,0, 1,0, 3,0, 1,0,3,0, 1,0 };
|
||||
|
||||
|
||||
char const * buffstrs[20] = { "1ch", "2ch", "3ch", "4ch", "RGBA", "ARGB", "RA", "AR", "RGBA_both_pre", "ARGB_both_pre", "RA_both_pre", "AR_both_pre", "RGBA_out_pre", "ARGB_out_pre", "RA_out_pre", "AR_out_pre", "RGBA_in_pre", "ARGB_in_pre", "RA_in_pre", "AR_in_pre" };
|
||||
char const * typestrs[5] = { "Bytes", "BytesSRGB", "Shorts", "Floats", "Half Floats"};
|
||||
char const * edgestrs[4] = { "Clamp", "Reflect", "Zero", "Wrap" };
|
||||
char const * fltstrs[5] = { "Box", "Triangle", "Cubic", "Catmullrom", "Mitchell" };
|
||||
|
||||
#ifdef STBIR_PROFILE
|
||||
static void do_acc_zones( STBIR_PROFILE_INFO * profile )
|
||||
{
|
||||
stbir_uint32 j;
|
||||
stbir_uint64 start = tmGetAccumulationStart( tm_mask ); start=start;
|
||||
|
||||
for( j = 0 ; j < profile->count ; j++ )
|
||||
{
|
||||
if ( profile->clocks[j] )
|
||||
tmEmitAccumulationZone( 0, 0, (tm_uint64*)&start, 0, profile->clocks[j], profile->descriptions[j] );
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define do_acc_zones(...)
|
||||
#endif
|
||||
|
||||
int64 vert;
|
||||
|
||||
//#define WINTHREADTEST
|
||||
#ifdef WINTHREADTEST
|
||||
|
||||
static STBIR_RESIZE * thread_resize;
|
||||
static LONG which;
|
||||
static int threads_started = 0;
|
||||
static HANDLE threads[32];
|
||||
static HANDLE starts,stops;
|
||||
|
||||
static DWORD resize_shim( LPVOID p )
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
LONG wh;
|
||||
|
||||
WaitForSingleObject( starts, INFINITE );
|
||||
|
||||
wh = InterlockedAdd( &which, 1 ) - 1;
|
||||
|
||||
ENTER( "Split %d", wh );
|
||||
stbir_resize_split( thread_resize, wh, 1 );
|
||||
#ifdef STBIR_PROFILE
|
||||
{ STBIR_PROFILE_INFO profile; stbir_resize_split_profile_info( &profile, thread_resize, wh, 1 ); do_acc_zones( &profile ); vert = profile.clocks[1]; }
|
||||
#endif
|
||||
LEAVE();
|
||||
|
||||
ReleaseSemaphore( stops, 1, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void nresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt )
|
||||
{
|
||||
STBIR_RESIZE resize;
|
||||
|
||||
stbir_resize_init( &resize, i, ix, iy, ip, o, ox, oy, op, buffers[buf], ttypes[type] );
|
||||
stbir_set_pixel_layouts( &resize, buffers[buf], obuffers[buf] );
|
||||
stbir_set_edgemodes( &resize, cedges[edg], cedges[edg] );
|
||||
stbir_set_filters( &resize, flts[flt], /*STBIR_FILTER_POINT_SAMPLE */ flts[flt] );
|
||||
//stbir_set_input_subrect( &resize, 0.55f,0.333f,0.75f,0.50f);
|
||||
//stbir_set_output_pixel_subrect( &resize, 00, 00, ox/2,oy/2);
|
||||
//stbir_set_pixel_subrect(&resize, 1430,1361,30,30);
|
||||
|
||||
ENTER( "Resize" );
|
||||
|
||||
#ifndef WINTHREADTEST
|
||||
|
||||
ENTER( "Filters" );
|
||||
stbir_build_samplers_with_splits( &resize, 1 );
|
||||
#ifdef STBIR_PROFILE
|
||||
{ STBIR_PROFILE_INFO profile; stbir_resize_build_profile_info( &profile, &resize ); do_acc_zones( &profile ); }
|
||||
#endif
|
||||
LEAVE();
|
||||
|
||||
ENTER( "Resize" );
|
||||
if(!stbir_resize_extended( &resize ) )
|
||||
stop();
|
||||
#ifdef STBIR_PROFILE
|
||||
{ STBIR_PROFILE_INFO profile; stbir_resize_extended_profile_info( &profile, &resize ); do_acc_zones( &profile ); vert = profile.clocks[1]; }
|
||||
#endif
|
||||
LEAVE();
|
||||
|
||||
#else
|
||||
{
|
||||
int c, cnt;
|
||||
|
||||
ENTER( "Filters" );
|
||||
cnt = stbir_build_samplers_with_splits( &resize, 4 );
|
||||
#ifdef STBIR_PROFILE
|
||||
{ STBIR_PROFILE_INFO profile; stbir_resize_build_profile_info( &profile, &resize ); do_acc_zones( &profile ); }
|
||||
#endif
|
||||
LEAVE();
|
||||
|
||||
ENTER( "Thread start" );
|
||||
if ( threads_started == 0 )
|
||||
{
|
||||
starts = CreateSemaphore( 0, 0, 32, 0 );
|
||||
stops = CreateSemaphore( 0, 0, 32, 0 );
|
||||
}
|
||||
for( c = threads_started ; c < cnt ; c++ )
|
||||
threads[ c ] = CreateThread( 0, 2048*1024, resize_shim, 0, 0, 0 );
|
||||
|
||||
threads_started = cnt;
|
||||
thread_resize = &resize;
|
||||
which = 0;
|
||||
LEAVE();
|
||||
|
||||
// starts the threads
|
||||
ReleaseSemaphore( starts, cnt, 0 );
|
||||
|
||||
ENTER( "Wait" );
|
||||
for( c = 0 ; c < cnt; c++ )
|
||||
WaitForSingleObject( stops, INFINITE );
|
||||
LEAVE();
|
||||
}
|
||||
#endif
|
||||
|
||||
ENTER( "Free" );
|
||||
stbir_free_samplers( &resize );
|
||||
LEAVE();
|
||||
LEAVE();
|
||||
}
|
||||
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
extern void oresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt );
|
||||
|
||||
|
||||
|
||||
#define TYPESTART 0
|
||||
#define TYPEEND 4
|
||||
|
||||
#define LAYOUTSTART 0
|
||||
#define LAYOUTEND 19
|
||||
|
||||
#define SIZEWSTART 0
|
||||
#define SIZEWEND 2
|
||||
|
||||
#define SIZEHSTART 0
|
||||
#define SIZEHEND 2
|
||||
|
||||
#define EDGESTART 0
|
||||
#define EDGEEND 3
|
||||
|
||||
#define FILTERSTART 0
|
||||
#define FILTEREND 4
|
||||
|
||||
#define HEIGHTSTART 0
|
||||
#define HEIGHTEND 2
|
||||
|
||||
#define WIDTHSTART 0
|
||||
#define WIDTHEND 2
|
||||
|
||||
|
||||
|
||||
|
||||
static void * convert8to16( unsigned char * i, int w, int h, int c )
|
||||
{
|
||||
unsigned short * ret;
|
||||
int p;
|
||||
|
||||
ret = malloc( w*h*c*sizeof(short) );
|
||||
for(p = 0 ; p < (w*h*c) ; p++ )
|
||||
{
|
||||
ret[p]=(short)((((int)i[p])<<8)+i[p]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void * convert8tof( unsigned char * i, int w, int h, int c )
|
||||
{
|
||||
float * ret;
|
||||
int p;
|
||||
|
||||
ret = malloc( w*h*c*sizeof(float) );
|
||||
for(p = 0 ; p < (w*h*c) ; p++ )
|
||||
{
|
||||
ret[p]=((float)i[p])*(1.0f/255.0f);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void * convert8tohf( unsigned char * i, int w, int h, int c )
|
||||
{
|
||||
stbir__FP16 * ret;
|
||||
int p;
|
||||
|
||||
ret = malloc( w*h*c*sizeof(stbir__FP16) );
|
||||
for(p = 0 ; p < (w*h*c) ; p++ )
|
||||
{
|
||||
ret[p]=stbir__float_to_half(((float)i[p])*(1.0f/255.0f));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void * convert8tohff( unsigned char * i, int w, int h, int c )
|
||||
{
|
||||
float * ret;
|
||||
int p;
|
||||
|
||||
ret = malloc( w*h*c*sizeof(float) );
|
||||
for(p = 0 ; p < (w*h*c) ; p++ )
|
||||
{
|
||||
ret[p]=stbir__half_to_float(stbir__float_to_half(((float)i[p])*(1.0f/255.0f)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isprime( int v )
|
||||
{
|
||||
int i;
|
||||
|
||||
if ( v <= 3 )
|
||||
return ( v > 1 );
|
||||
if ( ( v & 1 ) == 0 )
|
||||
return 0;
|
||||
if ( ( v % 3 ) == 0 )
|
||||
return 0;
|
||||
i = 5;
|
||||
while ( (i*i) <= v )
|
||||
{
|
||||
if ( ( v % i ) == 0 )
|
||||
return 0;
|
||||
if ( ( v % ( i + 2 ) ) == 0 )
|
||||
return 0;
|
||||
i += 6;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int getprime( int v )
|
||||
{
|
||||
int i;
|
||||
i = 0;
|
||||
for(;;)
|
||||
{
|
||||
if ( i >= v )
|
||||
return v; // can't find any, just return orig
|
||||
if (isprime(v - i))
|
||||
return v - i;
|
||||
if (isprime(v + i))
|
||||
return v + i;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
int ix, iy, ic;
|
||||
unsigned char * input[6];
|
||||
char * ir1;
|
||||
char * ir2;
|
||||
int szhs[3];
|
||||
int szws[3];
|
||||
int aw, ah, ac;
|
||||
unsigned char * correctalpha;
|
||||
int layouts, types, heights, widths, edges, filters;
|
||||
|
||||
if ( argc != 2 )
|
||||
{
|
||||
printf("command: stbirtest [imagefile]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SetupTM( "127.0.0.1" );
|
||||
|
||||
correctalpha = stbi_load( "correctalpha.png", &aw, &ah, &ac, 0 );
|
||||
|
||||
input[0] = stbi_load( argv[1], &ix, &iy, &ic, 0 );
|
||||
input[1] = input[0];
|
||||
input[2] = convert8to16( input[0], ix, iy, ic );
|
||||
input[3] = convert8tof( input[0], ix, iy, ic );
|
||||
input[4] = convert8tohf( input[0], ix, iy, ic );
|
||||
input[5] = convert8tohff( input[0], ix, iy, ic );
|
||||
|
||||
printf("Input %dx%d (%d channels)\n",ix,iy,ic);
|
||||
|
||||
ir1 = malloc( 4 * 4 * 3000 * 3000ULL );
|
||||
ir2 = malloc( 4 * 4 * 3000 * 3000ULL );
|
||||
|
||||
szhs[0] = getprime( iy/SHRINKBYH );
|
||||
szhs[1] = iy;
|
||||
szhs[2] = getprime( iy*ZOOMBYH );
|
||||
|
||||
szws[0] = getprime( ix/SHRINKBYW );
|
||||
szws[1] = ix;
|
||||
szws[2] = getprime( ix*ZOOMBYW );
|
||||
|
||||
#if 1
|
||||
for( types = TYPESTART ; types <= TYPEEND ; types++ )
|
||||
#else
|
||||
for( types = 1 ; types <= 1 ; types++ )
|
||||
#endif
|
||||
{
|
||||
ENTER( "Test type: %s",typestrs[types]);
|
||||
#if 1
|
||||
for( layouts = LAYOUTSTART ; layouts <= LAYOUTEND ; layouts++ )
|
||||
#else
|
||||
for( layouts = 16; layouts <= 16 ; layouts++ )
|
||||
#endif
|
||||
{
|
||||
ENTER( "Test layout: %s",buffstrs[layouts]);
|
||||
|
||||
#if 0
|
||||
for( heights = HEIGHTSTART ; heights <= HEIGHTEND ; heights++ )
|
||||
{
|
||||
int w, h = szhs[heights];
|
||||
#else
|
||||
for( heights = 0 ; heights <= 11 ; heights++ )
|
||||
{
|
||||
static int szhsz[12]={32, 200, 350, 400, 450, 509, 532, 624, 700, 824, 1023, 2053 };
|
||||
int w, h = szhsz[heights];
|
||||
#endif
|
||||
|
||||
ENTER( "Test height: %d %s %d",iy,(h<iy)?"Down":((h>iy)?"Up":"Same"),h);
|
||||
|
||||
#if 0
|
||||
for( widths = WIDTHSTART ; widths <= WIDTHEND ; widths++ )
|
||||
{
|
||||
w = szws[widths];
|
||||
#else
|
||||
for( widths = 0 ; widths <= 12 ; widths++ )
|
||||
{
|
||||
static int szwsz[13]={2, 32, 200, 350, 400, 450, 509, 532, 624, 700, 824, 1023, 2053 };
|
||||
w = szwsz[widths];
|
||||
#endif
|
||||
|
||||
ENTER( "Test width: %d %s %d",ix, (w<ix)?"Down":((w>ix)?"Up":"Same"), w);
|
||||
|
||||
#if 0
|
||||
for( edges = EDGESTART ; edges <= EDGEEND ; edges++ )
|
||||
#else
|
||||
for( edges = 0 ; edges <= 0 ; edges++ )
|
||||
#endif
|
||||
{
|
||||
ENTER( "Test edge: %s",edgestrs[edges]);
|
||||
#if 0
|
||||
for( filters = FILTERSTART ; filters <= FILTEREND ; filters++ )
|
||||
#else
|
||||
for( filters = 3 ; filters <= 3 ; filters++ )
|
||||
#endif
|
||||
{
|
||||
int op, opw, np,npw, c, a;
|
||||
#ifdef COMPARE_SAME
|
||||
int oldtypes = types;
|
||||
#else
|
||||
int oldtypes = (types==4)?3:types;
|
||||
#endif
|
||||
|
||||
ENTER( "Test filter: %s",fltstrs[filters]);
|
||||
{
|
||||
c = bchannels[layouts];
|
||||
a = alphapos[layouts];
|
||||
|
||||
op = w*tsizes[oldtypes]*c + 60;
|
||||
opw = w*tsizes[oldtypes]*c;
|
||||
|
||||
np = w*tsizes[types]*c + 60;
|
||||
npw = w*tsizes[types]*c;
|
||||
|
||||
printf( "%s:layout: %s w: %d h: %d edge: %s filt: %s\n", typestrs[types],buffstrs[layouts], w, h, edgestrs[edges], fltstrs[filters] );
|
||||
|
||||
|
||||
// clear pixel area to different, right edge to zero
|
||||
#ifndef NOCLEAR
|
||||
ENTER( "Test clear padding" );
|
||||
{
|
||||
int d;
|
||||
for( d = 0 ; d < h ; d++ )
|
||||
{
|
||||
int oofs = d * op;
|
||||
int nofs = d * np;
|
||||
memset( ir1 + oofs, 192, opw );
|
||||
memset( ir1 + oofs+opw, 79, op-opw );
|
||||
memset( ir2 + nofs, 255, npw );
|
||||
memset( ir2 + nofs+npw, 79, np-npw );
|
||||
}
|
||||
}
|
||||
LEAVE();
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef COMPARE_SAME
|
||||
#define TIMINGS 1
|
||||
#else
|
||||
#define TIMINGS 1
|
||||
#endif
|
||||
ENTER( "Test both" );
|
||||
{
|
||||
#ifndef PROFILE_NEW_ONLY
|
||||
{
|
||||
int ttt, max = 0x7fffffff;
|
||||
ENTER( "Test old" );
|
||||
for( ttt = 0 ; ttt < TIMINGS ; ttt++ )
|
||||
{
|
||||
int64 m = __cycles();
|
||||
|
||||
oresize( ir1, w, h, op,
|
||||
#ifdef COMPARE_SAME
|
||||
input[types],
|
||||
#else
|
||||
input[(types==4)?5:types],
|
||||
#endif
|
||||
ix, iy, ix*ic*tsizes[oldtypes], layouts, oldtypes, edges, filters );
|
||||
|
||||
m = __cycles() - m;
|
||||
if ( ( (int)m ) < max )
|
||||
max = (int) m;
|
||||
}
|
||||
LEAVE();
|
||||
printf("old: %d\n", max );
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
int ttt, max = 0x7fffffff, maxv = 0x7fffffff;
|
||||
ENTER( "Test new" );
|
||||
for( ttt = 0 ; ttt < TIMINGS ; ttt++ )
|
||||
{
|
||||
int64 m = __cycles();
|
||||
|
||||
nresize( ir2, w, h, np, input[types], ix, iy, ix*ic*tsizes[types], layouts, types, edges, filters );
|
||||
|
||||
m = __cycles() - m;
|
||||
if ( ( (int)m ) < max )
|
||||
max = (int) m;
|
||||
if ( ( (int)vert ) < maxv )
|
||||
maxv = (int) vert;
|
||||
}
|
||||
LEAVE(); // test new
|
||||
printf("new: %d (v: %d)\n", max, maxv );
|
||||
}
|
||||
}
|
||||
LEAVE(); // test both
|
||||
|
||||
if ( mem_count!= 0 )
|
||||
stop();
|
||||
|
||||
#ifndef NOCOMP
|
||||
ENTER( "Test compare" );
|
||||
{
|
||||
int x,y,ch;
|
||||
int nums = 0;
|
||||
for( y = 0 ; y < h ; y++ )
|
||||
{
|
||||
for( x = 0 ; x < w ; x++ )
|
||||
{
|
||||
switch(types)
|
||||
{
|
||||
case 0:
|
||||
case 1: //SRGB
|
||||
{
|
||||
unsigned char * p1 = (unsigned char *)&ir1[y*op+x*c];
|
||||
unsigned char * p2 = (unsigned char *)&ir2[y*np+x*c];
|
||||
for( ch = 0 ; ch < c ; ch++ )
|
||||
{
|
||||
float pp1,pp2,d;
|
||||
float av = (a==-1)?1.0f:((float)p1[a]/255.0f);
|
||||
|
||||
pp1 = p1[ch];
|
||||
pp2 = p2[ch];
|
||||
|
||||
// compare in premult space
|
||||
#ifndef COMPARE_SAME
|
||||
if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >=16 ) && ( layouts <= 19 ) ) )
|
||||
{
|
||||
pp1 *= av;
|
||||
pp2 *= av;
|
||||
}
|
||||
#endif
|
||||
|
||||
d = pp1 - pp2;
|
||||
if ( d < 0 ) d = -d;
|
||||
|
||||
#ifdef COMPARE_SAME
|
||||
if ( d > 0 )
|
||||
#else
|
||||
if ( d > 1 )
|
||||
#endif
|
||||
{
|
||||
printf("Error at %d x %d (chan %d) (d: %g a: %g) [%d %d %d %d] [%d %d %d %d]\n",x,y,ch, d,av, p1[0],p1[1],p1[2],p1[3], p2[0],p2[1],p2[2],p2[3]);
|
||||
++nums;
|
||||
if ( nums > 16 ) goto ex;
|
||||
//if (d) exit(1);
|
||||
//goto ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
unsigned short * p1 = (unsigned short *)&ir1[y*op+x*c*sizeof(short)];
|
||||
unsigned short * p2 = (unsigned short *)&ir2[y*np+x*c*sizeof(short)];
|
||||
for( ch = 0 ; ch < c ; ch++ )
|
||||
{
|
||||
float thres,pp1,pp2,d;
|
||||
float av = (a==-1)?1.0f:((float)p1[a]/65535.0f);
|
||||
|
||||
pp1 = p1[ch];
|
||||
pp2 = p2[ch];
|
||||
|
||||
// compare in premult space
|
||||
#ifndef COMPARE_SAME
|
||||
if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >= 16 ) && ( layouts <= 19 ) ) )
|
||||
{
|
||||
pp1 *= av;
|
||||
pp2 *= av;
|
||||
}
|
||||
#endif
|
||||
|
||||
d = pp1 - pp2;
|
||||
if ( d < 0 ) d = -d;
|
||||
|
||||
thres=((float)p1[ch]*0.007f)+2.0f;
|
||||
if (thres<4) thres = 4;
|
||||
|
||||
#ifdef COMPARE_SAME
|
||||
if ( d > 0 )
|
||||
#else
|
||||
if ( d > thres)
|
||||
#endif
|
||||
{
|
||||
printf("Error at %d x %d (chan %d) %d %d [df: %g th: %g al: %g] (%d %d %d %d) (%d %d %d %d)\n",x,y,ch, p1[ch],p2[ch],d,thres,av,p1[0],p1[1],p1[2],p1[3],p2[0],p2[1],p2[2],p2[3]);
|
||||
++nums;
|
||||
if ( nums > 16 ) goto ex;
|
||||
//if (d) exit(1);
|
||||
//goto ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
float * p1 = (float *)&ir1[y*op+x*c*sizeof(float)];
|
||||
float * p2 = (float *)&ir2[y*np+x*c*sizeof(float)];
|
||||
for( ch = 0 ; ch < c ; ch++ )
|
||||
{
|
||||
float pp1 = p1[ch], pp2 = p2[ch];
|
||||
float av = (a==-1)?1.0f:p1[a];
|
||||
float thres, d;
|
||||
|
||||
// clamp
|
||||
if (pp1<=0.0f) pp1 = 0;
|
||||
if (pp2<=0.0f) pp2 = 0;
|
||||
if (av<=0.0f) av = 0;
|
||||
if (pp1>1.0f) pp1 = 1.0f;
|
||||
if (pp2>1.0f) pp2 = 1.0f;
|
||||
if (av>1.0f) av = 1.0f;
|
||||
|
||||
// compare in premult space
|
||||
#ifndef COMPARE_SAME
|
||||
if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >= 16 ) && ( layouts <= 19 ) ) )
|
||||
{
|
||||
pp1 *= av;
|
||||
pp2 *= av;
|
||||
}
|
||||
#endif
|
||||
|
||||
d = pp1 - pp2;
|
||||
if ( d < 0 ) d = -d;
|
||||
|
||||
thres=(p1[ch]*0.002f)+0.0002f;
|
||||
if ( thres < 0 ) thres = -thres;
|
||||
|
||||
#ifdef COMPARE_SAME
|
||||
if ( d != 0.0f )
|
||||
#else
|
||||
if ( d > thres )
|
||||
#endif
|
||||
{
|
||||
printf("Error at %d x %d (chan %d) %g %g [df: %g th: %g al: %g] (%g %g %g %g) (%g %g %g %g)\n",x,y,ch, p1[ch],p2[ch],d,thres,av,p1[0],p1[1],p1[2],p1[3],p2[0],p2[1],p2[2],p2[3]);
|
||||
++nums;
|
||||
if ( nums > 16 ) goto ex;
|
||||
//if (d) exit(1);
|
||||
//goto ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
#ifdef COMPARE_SAME
|
||||
stbir__FP16 * p1 = (stbir__FP16 *)&ir1[y*op+x*c*sizeof(stbir__FP16)];
|
||||
#else
|
||||
float * p1 = (float *)&ir1[y*op+x*c*sizeof(float)];
|
||||
#endif
|
||||
stbir__FP16 * p2 = (stbir__FP16 *)&ir2[y*np+x*c*sizeof(stbir__FP16)];
|
||||
for( ch = 0 ; ch < c ; ch++ )
|
||||
{
|
||||
#ifdef COMPARE_SAME
|
||||
float pp1 = stbir__half_to_float(p1[ch]);
|
||||
float av = (a==-1)?1.0f:stbir__half_to_float(p1[a]);
|
||||
#else
|
||||
float pp1 = stbir__half_to_float(stbir__float_to_half(p1[ch]));
|
||||
float av = (a==-1)?1.0f:stbir__half_to_float(stbir__float_to_half(p1[a]));
|
||||
#endif
|
||||
float pp2 = stbir__half_to_float(p2[ch]);
|
||||
float d, thres;
|
||||
|
||||
// clamp
|
||||
if (pp1<=0.0f) pp1 = 0;
|
||||
if (pp2<=0.0f) pp2 = 0;
|
||||
if (av<=0.0f) av = 0;
|
||||
if (pp1>1.0f) pp1 = 1.0f;
|
||||
if (pp2>1.0f) pp2 = 1.0f;
|
||||
if (av>1.0f) av = 1.0f;
|
||||
|
||||
thres=(pp1*0.002f)+0.0002f;
|
||||
|
||||
// compare in premult space
|
||||
#ifndef COMPARE_SAME
|
||||
if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >= 16 ) && ( layouts <= 19 ) ) )
|
||||
{
|
||||
pp1 *= av;
|
||||
pp2 *= av;
|
||||
}
|
||||
#endif
|
||||
|
||||
d = pp1 - pp2;
|
||||
if ( d < 0 ) d = -d;
|
||||
|
||||
|
||||
#ifdef COMPARE_SAME
|
||||
if ( d != 0.0f )
|
||||
#else
|
||||
if ( d > thres )
|
||||
#endif
|
||||
{
|
||||
printf("Error at %d x %d (chan %d) %g %g [df: %g th: %g al: %g] (%g %g %g %g) (%g %g %g %g)\n",x,y,ch,
|
||||
#ifdef COMPARE_SAME
|
||||
stbir__half_to_float(p1[ch]),
|
||||
#else
|
||||
p1[ch],
|
||||
#endif
|
||||
stbir__half_to_float(p2[ch]),
|
||||
d,thres,av,
|
||||
#ifdef COMPARE_SAME
|
||||
stbir__half_to_float(p1[0]),stbir__half_to_float(p1[1]),stbir__half_to_float(p1[2]),stbir__half_to_float(p1[3]),
|
||||
#else
|
||||
p1[0],p1[1],p1[2],p1[3],
|
||||
#endif
|
||||
stbir__half_to_float(p2[0]),stbir__half_to_float(p2[1]),stbir__half_to_float(p2[2]),stbir__half_to_float(p2[3]) );
|
||||
++nums;
|
||||
if ( nums > 16 ) goto ex;
|
||||
//if (d) exit(1);
|
||||
//goto ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for( x = (w*c)*tsizes[oldtypes]; x < op; x++ )
|
||||
{
|
||||
if ( ir1[y*op+x] != 79 )
|
||||
{
|
||||
printf("Margin error at %d x %d %d (should be 79) OLD!\n",x,y,(unsigned char)ir1[y*op+x]);
|
||||
goto ex;
|
||||
}
|
||||
}
|
||||
|
||||
for( x = (w*c)*tsizes[types]; x < np; x++ )
|
||||
{
|
||||
if ( ir2[y*np+x] != 79 )
|
||||
{
|
||||
printf("Margin error at %d x %d %d (should be 79) NEW\n",x,y,(unsigned char)ir2[y*np+x]);
|
||||
goto ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ex:
|
||||
ENTER( "OUTPUT IMAGES" );
|
||||
printf(" tot pix: %d, errs: %d\n", w*h*c,nums );
|
||||
|
||||
if (nums)
|
||||
{
|
||||
stbi_write_png("old.png", w, h, c, ir1, op);
|
||||
stbi_write_png("new.png", w, h, c, ir2, np);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
LEAVE(); // output images
|
||||
}
|
||||
LEAVE(); //test compare
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
}
|
||||
LEAVE(); // test filter
|
||||
}
|
||||
LEAVE(); // test edge
|
||||
}
|
||||
LEAVE(); // test width
|
||||
}
|
||||
LEAVE(); // test height
|
||||
}
|
||||
LEAVE(); // test type
|
||||
}
|
||||
LEAVE(); // test layout
|
||||
}
|
||||
|
||||
CloseTM();
|
||||
return 0;
|
||||
}
|
999
external/stb/stb/stb_image_resize_test/vf_train.c
vendored
Normal file
999
external/stb/stb/stb_image_resize_test/vf_train.c
vendored
Normal file
@ -0,0 +1,999 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define stop() __debugbreak()
|
||||
#include <windows.h>
|
||||
#define int64 __int64
|
||||
|
||||
#pragma warning(disable:4127)
|
||||
|
||||
#define STBIR__WEIGHT_TABLES
|
||||
#define STBIR_PROFILE
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "stb_image_resize2.h"
|
||||
|
||||
static int * file_read( char const * filename )
|
||||
{
|
||||
size_t s;
|
||||
int * m;
|
||||
FILE * f = fopen( filename, "rb" );
|
||||
if ( f == 0 ) return 0;
|
||||
|
||||
fseek( f, 0, SEEK_END);
|
||||
s = ftell( f );
|
||||
fseek( f, 0, SEEK_SET);
|
||||
m = malloc( s + 4 );
|
||||
m[0] = (int)s;
|
||||
fread( m+1, 1, s, f);
|
||||
fclose(f);
|
||||
|
||||
return( m );
|
||||
}
|
||||
|
||||
typedef struct fileinfo
|
||||
{
|
||||
int * timings;
|
||||
int timing_count;
|
||||
int dimensionx, dimensiony;
|
||||
int numtypes;
|
||||
int * types;
|
||||
int * effective;
|
||||
int cpu;
|
||||
int simd;
|
||||
int numinputrects;
|
||||
int * inputrects;
|
||||
int outputscalex, outputscaley;
|
||||
int milliseconds;
|
||||
int64 cycles;
|
||||
double scale_time;
|
||||
int bitmapx, bitmapy;
|
||||
char const * filename;
|
||||
} fileinfo;
|
||||
|
||||
int numfileinfo;
|
||||
fileinfo fi[256];
|
||||
unsigned char * bitmap;
|
||||
int bitmapw, bitmaph, bitmapp;
|
||||
|
||||
static int use_timing_file( char const * filename, int index )
|
||||
{
|
||||
int * base = file_read( filename );
|
||||
int * file = base;
|
||||
|
||||
if ( base == 0 ) return 0;
|
||||
|
||||
++file; // skip file image size;
|
||||
if ( *file++ != 'VFT1' ) return 0;
|
||||
fi[index].cpu = *file++;
|
||||
fi[index].simd = *file++;
|
||||
fi[index].dimensionx = *file++;
|
||||
fi[index].dimensiony = *file++;
|
||||
fi[index].numtypes = *file++;
|
||||
fi[index].types = file; file += fi[index].numtypes;
|
||||
fi[index].effective = file; file += fi[index].numtypes;
|
||||
fi[index].numinputrects = *file++;
|
||||
fi[index].inputrects = file; file += fi[index].numinputrects * 2;
|
||||
fi[index].outputscalex = *file++;
|
||||
fi[index].outputscaley = *file++;
|
||||
fi[index].milliseconds = *file++;
|
||||
fi[index].cycles = ((int64*)file)[0]; file += 2;
|
||||
fi[index].filename = filename;
|
||||
|
||||
fi[index].timings = file;
|
||||
fi[index].timing_count = (int) ( ( base[0] - ( ((char*)file - (char*)base - sizeof(int) ) ) ) / (sizeof(int)*2) );
|
||||
|
||||
fi[index].scale_time = (double)fi[index].milliseconds / (double)fi[index].cycles;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int vert_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int ox, int oy, int ix, int iy, int filter, STBIR__V_FIRST_INFO * v_info )
|
||||
{
|
||||
float h_scale=(float)ox/(float)(ix);
|
||||
float v_scale=(float)oy/(float)(iy);
|
||||
stbir__support_callback * support = stbir__builtin_supports[filter];
|
||||
int vertical_filter_width = stbir__get_filter_pixel_width(support,v_scale,0);
|
||||
int vertical_gather = ( v_scale >= ( 1.0f - stbir__small_float ) ) || ( vertical_filter_width <= STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT );
|
||||
|
||||
return stbir__should_do_vertical_first( weights_table, stbir__get_filter_pixel_width(support,h_scale,0), h_scale, ox, vertical_filter_width, v_scale, oy, vertical_gather, v_info );
|
||||
}
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
|
||||
static void alloc_bitmap()
|
||||
{
|
||||
int findex;
|
||||
int x = 0, y = 0;
|
||||
int w = 0, h = 0;
|
||||
|
||||
for( findex = 0 ; findex < numfileinfo ; findex++ )
|
||||
{
|
||||
int nx, ny;
|
||||
int thisw, thish;
|
||||
|
||||
thisw = ( fi[findex].dimensionx * fi[findex].numtypes ) + ( fi[findex].numtypes - 1 );
|
||||
thish = ( fi[findex].dimensiony * fi[findex].numinputrects ) + ( fi[findex].numinputrects - 1 );
|
||||
|
||||
for(;;)
|
||||
{
|
||||
nx = x + ((x)?4:0) + thisw;
|
||||
ny = y + ((y)?4:0) + thish;
|
||||
if ( ( nx <= 3600 ) || ( x == 0 ) )
|
||||
{
|
||||
fi[findex].bitmapx = x + ((x)?4:0);
|
||||
fi[findex].bitmapy = y + ((y)?4:0);
|
||||
x = nx;
|
||||
if ( x > w ) w = x;
|
||||
if ( ny > h ) h = ny;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = 0;
|
||||
y = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w = (w+3) & ~3;
|
||||
bitmapw = w;
|
||||
bitmaph = h;
|
||||
bitmapp = w * 3; // RGB
|
||||
bitmap = malloc( bitmapp * bitmaph );
|
||||
|
||||
memset( bitmap, 0, bitmapp * bitmaph );
|
||||
}
|
||||
|
||||
static void build_bitmap( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int do_channel_count_index, int findex )
|
||||
{
|
||||
static int colors[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
STBIR__V_FIRST_INFO v_info = {0};
|
||||
|
||||
int * ts;
|
||||
int ir;
|
||||
unsigned char * bitm = bitmap + ( fi[findex].bitmapx*3 ) + ( fi[findex].bitmapy*bitmapp) ;
|
||||
|
||||
for( ir = 0; ir < STBIR_RESIZE_CLASSIFICATIONS ; ir++ ) colors[ ir ] = 127*ir/STBIR_RESIZE_CLASSIFICATIONS+128;
|
||||
|
||||
ts = fi[findex].timings;
|
||||
|
||||
for( ir = 0 ; ir < fi[findex].numinputrects ; ir++ )
|
||||
{
|
||||
int ix, iy, chanind;
|
||||
ix = fi[findex].inputrects[ir*2];
|
||||
iy = fi[findex].inputrects[ir*2+1];
|
||||
|
||||
for( chanind = 0 ; chanind < fi[findex].numtypes ; chanind++ )
|
||||
{
|
||||
int ofs, h, hh;
|
||||
|
||||
// just do the type that we're on
|
||||
if ( chanind != do_channel_count_index )
|
||||
{
|
||||
ts += 2 * fi[findex].dimensionx * fi[findex].dimensiony;
|
||||
continue;
|
||||
}
|
||||
|
||||
// bitmap offset
|
||||
ofs=chanind*(fi[findex].dimensionx+1)*3+ir*(fi[findex].dimensiony+1)*bitmapp;
|
||||
|
||||
h = 1;
|
||||
for( hh = 0 ; hh < fi[findex].dimensiony; hh++ )
|
||||
{
|
||||
int ww, w = 1;
|
||||
for( ww = 0 ; ww < fi[findex].dimensionx; ww++ )
|
||||
{
|
||||
int good, v_first, VF, HF;
|
||||
|
||||
VF = ts[0];
|
||||
HF = ts[1];
|
||||
|
||||
v_first = vert_first( weights, w, h, ix, iy, STBIR_FILTER_MITCHELL, &v_info );
|
||||
|
||||
good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first)));
|
||||
|
||||
if ( good )
|
||||
{
|
||||
bitm[ofs+2] = 0;
|
||||
bitm[ofs+1] = (unsigned char)colors[v_info.v_resize_classification];
|
||||
}
|
||||
else
|
||||
{
|
||||
double r;
|
||||
|
||||
if ( HF < VF )
|
||||
r = (double)(VF-HF)/(double)HF;
|
||||
else
|
||||
r = (double)(HF-VF)/(double)VF;
|
||||
|
||||
if ( r > 0.4f) r = 0.4;
|
||||
r *= 1.0f/0.4f;
|
||||
|
||||
bitm[ofs+2] = (char)(255.0f*r);
|
||||
bitm[ofs+1] = (char)(((float)colors[v_info.v_resize_classification])*(1.0f-r));
|
||||
}
|
||||
bitm[ofs] = 0;
|
||||
|
||||
ofs += 3;
|
||||
ts += 2;
|
||||
w += fi[findex].outputscalex;
|
||||
}
|
||||
ofs += bitmapp - fi[findex].dimensionx*3;
|
||||
h += fi[findex].outputscaley;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void build_comp_bitmap( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int do_channel_count_index )
|
||||
{
|
||||
int * ts0;
|
||||
int * ts1;
|
||||
int ir;
|
||||
unsigned char * bitm = bitmap + ( fi[0].bitmapx*3 ) + ( fi[0].bitmapy*bitmapp) ;
|
||||
|
||||
ts0 = fi[0].timings;
|
||||
ts1 = fi[1].timings;
|
||||
|
||||
for( ir = 0 ; ir < fi[0].numinputrects ; ir++ )
|
||||
{
|
||||
int ix, iy, chanind;
|
||||
ix = fi[0].inputrects[ir*2];
|
||||
iy = fi[0].inputrects[ir*2+1];
|
||||
|
||||
for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ )
|
||||
{
|
||||
int ofs, h, hh;
|
||||
|
||||
// just do the type that we're on
|
||||
if ( chanind != do_channel_count_index )
|
||||
{
|
||||
ts0 += 2 * fi[0].dimensionx * fi[0].dimensiony;
|
||||
ts1 += 2 * fi[0].dimensionx * fi[0].dimensiony;
|
||||
continue;
|
||||
}
|
||||
|
||||
// bitmap offset
|
||||
ofs=chanind*(fi[0].dimensionx+1)*3+ir*(fi[0].dimensiony+1)*bitmapp;
|
||||
|
||||
h = 1;
|
||||
for( hh = 0 ; hh < fi[0].dimensiony; hh++ )
|
||||
{
|
||||
int ww, w = 1;
|
||||
for( ww = 0 ; ww < fi[0].dimensionx; ww++ )
|
||||
{
|
||||
int v_first, time0, time1;
|
||||
|
||||
v_first = vert_first( weights, w, h, ix, iy, STBIR_FILTER_MITCHELL, 0 );
|
||||
|
||||
time0 = ( v_first ) ? ts0[0] : ts0[1];
|
||||
time1 = ( v_first ) ? ts1[0] : ts1[1];
|
||||
|
||||
if ( time0 < time1 )
|
||||
{
|
||||
double r = (double)(time1-time0)/(double)time0;
|
||||
if ( r > 0.4f) r = 0.4;
|
||||
r *= 1.0f/0.4f;
|
||||
bitm[ofs+2] = 0;
|
||||
bitm[ofs+1] = (char)(255.0f*r);
|
||||
bitm[ofs] = (char)(64.0f*(1.0f-r));
|
||||
}
|
||||
else
|
||||
{
|
||||
double r = (double)(time0-time1)/(double)time1;
|
||||
if ( r > 0.4f) r = 0.4;
|
||||
r *= 1.0f/0.4f;
|
||||
bitm[ofs+2] = (char)(255.0f*r);
|
||||
bitm[ofs+1] = 0;
|
||||
bitm[ofs] = (char)(64.0f*(1.0f-r));
|
||||
}
|
||||
ofs += 3;
|
||||
ts0 += 2;
|
||||
ts1 += 2;
|
||||
w += fi[0].outputscalex;
|
||||
}
|
||||
ofs += bitmapp - fi[0].dimensionx*3;
|
||||
h += fi[0].outputscaley;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void write_bitmap()
|
||||
{
|
||||
stbi_write_png( "results.png", bitmapp / 3, bitmaph, 3|STB_IMAGE_BGR, bitmap, bitmapp );
|
||||
}
|
||||
|
||||
|
||||
static void calc_errors( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int * curtot, double * curerr, int do_channel_count_index )
|
||||
{
|
||||
int th, findex;
|
||||
STBIR__V_FIRST_INFO v_info = {0};
|
||||
|
||||
for(th=0;th<STBIR_RESIZE_CLASSIFICATIONS;th++)
|
||||
{
|
||||
curerr[th]=0;
|
||||
curtot[th]=0;
|
||||
}
|
||||
|
||||
for( findex = 0 ; findex < numfileinfo ; findex++ )
|
||||
{
|
||||
int * ts;
|
||||
int ir;
|
||||
ts = fi[findex].timings;
|
||||
|
||||
for( ir = 0 ; ir < fi[findex].numinputrects ; ir++ )
|
||||
{
|
||||
int ix, iy, chanind;
|
||||
ix = fi[findex].inputrects[ir*2];
|
||||
iy = fi[findex].inputrects[ir*2+1];
|
||||
|
||||
for( chanind = 0 ; chanind < fi[findex].numtypes ; chanind++ )
|
||||
{
|
||||
int h, hh;
|
||||
|
||||
// just do the type that we're on
|
||||
if ( chanind != do_channel_count_index )
|
||||
{
|
||||
ts += 2 * fi[findex].dimensionx * fi[findex].dimensiony;
|
||||
continue;
|
||||
}
|
||||
|
||||
h = 1;
|
||||
for( hh = 0 ; hh < fi[findex].dimensiony; hh++ )
|
||||
{
|
||||
int ww, w = 1;
|
||||
for( ww = 0 ; ww < fi[findex].dimensionx; ww++ )
|
||||
{
|
||||
int good, v_first, VF, HF;
|
||||
|
||||
VF = ts[0];
|
||||
HF = ts[1];
|
||||
|
||||
v_first = vert_first( weights_table, w, h, ix, iy, STBIR_FILTER_MITCHELL, &v_info );
|
||||
|
||||
good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first)));
|
||||
|
||||
if ( !good )
|
||||
{
|
||||
double diff;
|
||||
if ( VF < HF )
|
||||
diff = ((double)HF-(double)VF) * fi[findex].scale_time;
|
||||
else
|
||||
diff = ((double)VF-(double)HF) * fi[findex].scale_time;
|
||||
|
||||
curtot[v_info.v_resize_classification] += 1;
|
||||
curerr[v_info.v_resize_classification] += diff;
|
||||
}
|
||||
|
||||
ts += 2;
|
||||
w += fi[findex].outputscalex;
|
||||
}
|
||||
h += fi[findex].outputscaley;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define TRIESPERWEIGHT 32
|
||||
#define MAXRANGE ((TRIESPERWEIGHT+1) * (TRIESPERWEIGHT+1) * (TRIESPERWEIGHT+1) * (TRIESPERWEIGHT+1) - 1)
|
||||
|
||||
static void expand_to_floats( float * weights, int range )
|
||||
{
|
||||
weights[0] = (float)( range % (TRIESPERWEIGHT+1) ) / (float)TRIESPERWEIGHT;
|
||||
weights[1] = (float)( range/(TRIESPERWEIGHT+1) % (TRIESPERWEIGHT+1) ) / (float)TRIESPERWEIGHT;
|
||||
weights[2] = (float)( range/(TRIESPERWEIGHT+1)/(TRIESPERWEIGHT+1) % (TRIESPERWEIGHT+1) ) / (float)TRIESPERWEIGHT;
|
||||
weights[3] = (float)( range/(TRIESPERWEIGHT+1)/(TRIESPERWEIGHT+1)/(TRIESPERWEIGHT+1) % (TRIESPERWEIGHT+1) ) / (float)TRIESPERWEIGHT;
|
||||
}
|
||||
|
||||
static char const * expand_to_string( int range )
|
||||
{
|
||||
static char str[128];
|
||||
int w0,w1,w2,w3;
|
||||
w0 = range % (TRIESPERWEIGHT+1);
|
||||
w1 = range/(TRIESPERWEIGHT+1) % (TRIESPERWEIGHT+1);
|
||||
w2 = range/(TRIESPERWEIGHT+1)/(TRIESPERWEIGHT+1) % (TRIESPERWEIGHT+1);
|
||||
w3 = range/(TRIESPERWEIGHT+1)/(TRIESPERWEIGHT+1)/(TRIESPERWEIGHT+1) % (TRIESPERWEIGHT+1);
|
||||
sprintf( str, "[ %2d/%d %2d/%d %2d/%d %2d/%d ]",w0,TRIESPERWEIGHT,w1,TRIESPERWEIGHT,w2,TRIESPERWEIGHT,w3,TRIESPERWEIGHT );
|
||||
return str;
|
||||
}
|
||||
|
||||
static void print_weights( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int channel_count_index, int * tots, double * errs )
|
||||
{
|
||||
int th;
|
||||
printf("ChInd: %d Weights:\n",channel_count_index);
|
||||
for(th=0;th<STBIR_RESIZE_CLASSIFICATIONS;th++)
|
||||
{
|
||||
float * w = weights[th];
|
||||
printf(" %d: [%1.5f %1.5f %1.5f %1.5f] (%d %.4f)\n",th, w[0], w[1], w[2], w[3], tots[th], errs[th] );
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int windowranges[ 16 ];
|
||||
static int windowstatus = 0;
|
||||
static DWORD trainstart = 0;
|
||||
|
||||
static void opt_channel( float best_output_weights[STBIR_RESIZE_CLASSIFICATIONS][4], int channel_count_index )
|
||||
{
|
||||
int newbest = 0;
|
||||
float weights[STBIR_RESIZE_CLASSIFICATIONS][4] = {0};
|
||||
double besterr[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
int besttot[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
int best[STBIR_RESIZE_CLASSIFICATIONS]={0};
|
||||
|
||||
double curerr[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
int curtot[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
int th, range;
|
||||
DWORD lasttick = 0;
|
||||
|
||||
for(th=0;th<STBIR_RESIZE_CLASSIFICATIONS;th++)
|
||||
{
|
||||
besterr[th]=1000000000000.0;
|
||||
besttot[th]=0x7fffffff;
|
||||
}
|
||||
|
||||
newbest = 0;
|
||||
|
||||
// try the whole range
|
||||
range = MAXRANGE;
|
||||
do
|
||||
{
|
||||
for(th=0;th<STBIR_RESIZE_CLASSIFICATIONS;th++)
|
||||
expand_to_floats( weights[th], range );
|
||||
|
||||
calc_errors( weights, curtot, curerr, channel_count_index );
|
||||
|
||||
for(th=0;th<STBIR_RESIZE_CLASSIFICATIONS;th++)
|
||||
{
|
||||
if ( curerr[th] < besterr[th] )
|
||||
{
|
||||
besterr[th] = curerr[th];
|
||||
besttot[th] = curtot[th];
|
||||
best[th] = range;
|
||||
expand_to_floats( best_output_weights[th], best[th] );
|
||||
newbest = 1;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
DWORD t = GetTickCount();
|
||||
if ( range == 0 )
|
||||
goto do_bitmap;
|
||||
|
||||
if ( newbest )
|
||||
{
|
||||
if ( ( GetTickCount() - lasttick ) > 200 )
|
||||
{
|
||||
int findex;
|
||||
|
||||
do_bitmap:
|
||||
lasttick = t;
|
||||
newbest = 0;
|
||||
|
||||
for( findex = 0 ; findex < numfileinfo ; findex++ )
|
||||
build_bitmap( best_output_weights, channel_count_index, findex );
|
||||
|
||||
lasttick = GetTickCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
windowranges[ channel_count_index ] = range;
|
||||
|
||||
// advance all the weights and loop
|
||||
--range;
|
||||
} while( ( range >= 0 ) && ( !windowstatus ) );
|
||||
|
||||
// if we hit here, then we tried all weights for this opt, so save them
|
||||
}
|
||||
|
||||
static void print_struct( float weight[5][STBIR_RESIZE_CLASSIFICATIONS][4], char const * name )
|
||||
{
|
||||
printf("\n\nstatic float %s[5][STBIR_RESIZE_CLASSIFICATIONS][4]=\n{", name );
|
||||
{
|
||||
int i;
|
||||
for(i=0;i<5;i++)
|
||||
{
|
||||
int th;
|
||||
for(th=0;th<STBIR_RESIZE_CLASSIFICATIONS;th++)
|
||||
{
|
||||
int j;
|
||||
printf("\n ");
|
||||
for(j=0;j<4;j++)
|
||||
printf("%1.5ff, ", weight[i][th][j] );
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("\n};\n");
|
||||
}
|
||||
}
|
||||
|
||||
static float retrain_weights[5][STBIR_RESIZE_CLASSIFICATIONS][4];
|
||||
|
||||
static DWORD __stdcall retrain_shim( LPVOID p )
|
||||
{
|
||||
int chanind = (int) (size_t)p;
|
||||
opt_channel( retrain_weights[chanind], chanind );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char const * gettime( int ms )
|
||||
{
|
||||
static char time[32];
|
||||
if (ms > 60000)
|
||||
sprintf( time, "%dm %ds",ms/60000, (ms/1000)%60 );
|
||||
else
|
||||
sprintf( time, "%ds",ms/1000 );
|
||||
return time;
|
||||
}
|
||||
|
||||
static BITMAPINFOHEADER bmiHeader;
|
||||
static DWORD extrawindoww, extrawindowh;
|
||||
static HINSTANCE instance;
|
||||
static int curzoom = 1;
|
||||
|
||||
static LRESULT WINAPI WindowProc( HWND window,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam )
|
||||
{
|
||||
switch( message )
|
||||
{
|
||||
case WM_CHAR:
|
||||
if ( wparam != 27 )
|
||||
break;
|
||||
// falls through
|
||||
|
||||
case WM_CLOSE:
|
||||
{
|
||||
int i;
|
||||
int max = 0;
|
||||
|
||||
for( i = 0 ; i < fi[0].numtypes ; i++ )
|
||||
if( windowranges[i] > max ) max = windowranges[i];
|
||||
|
||||
if ( ( max == 0 ) || ( MessageBox( window, "Cancel before training is finished?", "Vertical First Training", MB_OKCANCEL|MB_ICONSTOP ) == IDOK ) )
|
||||
{
|
||||
for( i = 0 ; i < fi[0].numtypes ; i++ )
|
||||
if( windowranges[i] > max ) max = windowranges[i];
|
||||
if ( max )
|
||||
windowstatus = 1;
|
||||
DestroyWindow( window );
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_PAINT:
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc;
|
||||
|
||||
dc = BeginPaint( window, &ps );
|
||||
StretchDIBits( dc,
|
||||
0, 0, bitmapw*curzoom, bitmaph*curzoom,
|
||||
0, 0, bitmapw, bitmaph,
|
||||
bitmap, (BITMAPINFO*)&bmiHeader, DIB_RGB_COLORS, SRCCOPY );
|
||||
|
||||
PatBlt( dc, bitmapw*curzoom, 0, 4096, 4096, WHITENESS );
|
||||
PatBlt( dc, 0, bitmaph*curzoom, 4096, 4096, WHITENESS );
|
||||
|
||||
SetTextColor( dc, RGB(0,0,0) );
|
||||
SetBkColor( dc, RGB(255,255,255) );
|
||||
SetBkMode( dc, OPAQUE );
|
||||
|
||||
{
|
||||
int i, l = 0, max = 0;
|
||||
char buf[1024];
|
||||
RECT rc;
|
||||
POINT p;
|
||||
|
||||
for( i = 0 ; i < fi[0].numtypes ; i++ )
|
||||
{
|
||||
l += sprintf( buf + l, "channels: %d %s\n", fi[0].effective[i], windowranges[i] ? expand_to_string( windowranges[i] ) : "Done." );
|
||||
if ( windowranges[i] > max ) max = windowranges[i];
|
||||
}
|
||||
|
||||
rc.left = 32; rc.top = bitmaph*curzoom+10;
|
||||
rc.right = 512; rc.bottom = rc.top + 512;
|
||||
DrawText( dc, buf, -1, &rc, DT_TOP );
|
||||
|
||||
l = 0;
|
||||
if ( max == 0 )
|
||||
{
|
||||
static DWORD traindone = 0;
|
||||
if ( traindone == 0 ) traindone = GetTickCount();
|
||||
l = sprintf( buf, "Finished in %s.", gettime( traindone - trainstart ) );
|
||||
}
|
||||
else if ( max != MAXRANGE )
|
||||
l = sprintf( buf, "Done in %s...", gettime( (int) ( ( ( (int64)max * ( (int64)GetTickCount() - (int64)trainstart ) ) ) / (int64) ( MAXRANGE - max ) ) ) );
|
||||
|
||||
GetCursorPos( &p );
|
||||
ScreenToClient( window, &p );
|
||||
|
||||
if ( ( p.x >= 0 ) && ( p.y >= 0 ) && ( p.x < (bitmapw*curzoom) ) && ( p.y < (bitmaph*curzoom) ) )
|
||||
{
|
||||
int findex;
|
||||
int x, y, w, h, sx, sy, ix, iy, ox, oy;
|
||||
int ir, chanind;
|
||||
int * ts;
|
||||
char badstr[64];
|
||||
STBIR__V_FIRST_INFO v_info={0};
|
||||
|
||||
p.x /= curzoom;
|
||||
p.y /= curzoom;
|
||||
|
||||
for( findex = 0 ; findex < numfileinfo ; findex++ )
|
||||
{
|
||||
x = fi[findex].bitmapx;
|
||||
y = fi[findex].bitmapy;
|
||||
w = x + ( fi[findex].dimensionx + 1 ) * fi[findex].numtypes;
|
||||
h = y + ( fi[findex].dimensiony + 1 ) * fi[findex].numinputrects;
|
||||
|
||||
if ( ( p.x >= x ) && ( p.y >= y ) && ( p.x < w ) && ( p.y < h ) )
|
||||
goto found;
|
||||
}
|
||||
goto nope;
|
||||
|
||||
found:
|
||||
|
||||
ir = ( p.y - y ) / ( fi[findex].dimensiony + 1 );
|
||||
sy = ( p.y - y ) % ( fi[findex].dimensiony + 1 );
|
||||
if ( sy >= fi[findex].dimensiony ) goto nope;
|
||||
|
||||
chanind = ( p.x - x ) / ( fi[findex].dimensionx + 1 );
|
||||
sx = ( p.x - x ) % ( fi[findex].dimensionx + 1 );
|
||||
if ( sx >= fi[findex].dimensionx ) goto nope;
|
||||
|
||||
ix = fi[findex].inputrects[ir*2];
|
||||
iy = fi[findex].inputrects[ir*2+1];
|
||||
|
||||
ts = fi[findex].timings + ( ( fi[findex].dimensionx * fi[findex].dimensiony * fi[findex].numtypes * ir ) + ( fi[findex].dimensionx * fi[findex].dimensiony * chanind ) + ( fi[findex].dimensionx * sy ) + sx ) * 2;
|
||||
|
||||
ox = 1+fi[findex].outputscalex*sx;
|
||||
oy = 1+fi[findex].outputscaley*sy;
|
||||
|
||||
if ( windowstatus != 2 )
|
||||
{
|
||||
int VF, HF, v_first, good;
|
||||
VF = ts[0];
|
||||
HF = ts[1];
|
||||
|
||||
v_first = vert_first( retrain_weights[chanind], ox, oy, ix, iy, STBIR_FILTER_MITCHELL, &v_info );
|
||||
|
||||
good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first)));
|
||||
|
||||
if ( good )
|
||||
badstr[0] = 0;
|
||||
else
|
||||
{
|
||||
double r;
|
||||
|
||||
if ( HF < VF )
|
||||
r = (double)(VF-HF)/(double)HF;
|
||||
else
|
||||
r = (double)(HF-VF)/(double)VF;
|
||||
sprintf( badstr, " %.1f%% off", r*100 );
|
||||
}
|
||||
sprintf( buf + l, "\n\n%s\nCh: %d Resize: %dx%d to %dx%d\nV: %d H: %d Order: %c (%s%s)\nClass: %d Scale: %.2f %s", fi[findex].filename,fi[findex].effective[chanind], ix,iy,ox,oy, VF, HF, v_first?'V':'H', good?"Good":"Wrong", badstr, v_info.v_resize_classification, (double)oy/(double)iy, v_info.is_gather ? "Gather" : "Scatter" );
|
||||
}
|
||||
else
|
||||
{
|
||||
int v_first, time0, time1;
|
||||
float (* weights)[4] = stbir__compute_weights[chanind];
|
||||
int * ts1;
|
||||
char b0[32], b1[32];
|
||||
|
||||
ts1 = fi[1].timings + ( ts - fi[0].timings );
|
||||
|
||||
v_first = vert_first( weights, ox, oy, ix, iy, STBIR_FILTER_MITCHELL, &v_info );
|
||||
|
||||
time0 = ( v_first ) ? ts[0] : ts[1];
|
||||
time1 = ( v_first ) ? ts1[0] : ts1[1];
|
||||
|
||||
b0[0] = b1[0] = 0;
|
||||
if ( time0 < time1 )
|
||||
sprintf( b0," (%.f%% better)", ((double)time1-(double)time0)*100.0f/(double)time0);
|
||||
else
|
||||
sprintf( b1," (%.f%% better)", ((double)time0-(double)time1)*100.0f/(double)time1);
|
||||
|
||||
sprintf( buf + l, "\n\n0: %s\n1: %s\nCh: %d Resize: %dx%d to %dx%d\nClass: %d Scale: %.2f %s\nTime0: %d%s\nTime1: %d%s", fi[0].filename, fi[1].filename, fi[0].effective[chanind], ix,iy,ox,oy, v_info.v_resize_classification, (double)oy/(double)iy, v_info.is_gather ? "Gather" : "Scatter", time0, b0, time1, b1 );
|
||||
}
|
||||
}
|
||||
nope:
|
||||
|
||||
rc.left = 32+320; rc.right = 512+320;
|
||||
SetTextColor( dc, RGB(0,0,128) );
|
||||
DrawText( dc, buf, -1, &rc, DT_TOP );
|
||||
|
||||
}
|
||||
EndPaint( window, &ps );
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_TIMER:
|
||||
InvalidateRect( window, 0, 0 );
|
||||
return 0;
|
||||
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage( 0 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return DefWindowProc( window, message, wparam, lparam );
|
||||
}
|
||||
|
||||
static void SetHighDPI(void)
|
||||
{
|
||||
typedef HRESULT WINAPI setdpitype(int v);
|
||||
HMODULE h=LoadLibrary("Shcore.dll");
|
||||
if (h)
|
||||
{
|
||||
setdpitype * sd = (setdpitype*)GetProcAddress(h,"SetProcessDpiAwareness");
|
||||
if (sd )
|
||||
sd(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_window()
|
||||
{
|
||||
WNDCLASS wc;
|
||||
HWND w;
|
||||
MSG msg;
|
||||
|
||||
instance = GetModuleHandle(NULL);
|
||||
|
||||
wc.style = 0;
|
||||
wc.lpfnWndProc = WindowProc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = instance;
|
||||
wc.hIcon = 0;
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.hbrBackground = 0;
|
||||
wc.lpszMenuName = 0;
|
||||
wc.lpszClassName = "WHTrain";
|
||||
|
||||
if ( !RegisterClass( &wc ) )
|
||||
exit(1);
|
||||
|
||||
SetHighDPI();
|
||||
|
||||
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmiHeader.biWidth = bitmapp/3;
|
||||
bmiHeader.biHeight = -bitmaph;
|
||||
bmiHeader.biPlanes = 1;
|
||||
bmiHeader.biBitCount = 24;
|
||||
bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
w = CreateWindow( "WHTrain",
|
||||
"Vertical First Training",
|
||||
WS_CAPTION | WS_POPUP| WS_CLIPCHILDREN |
|
||||
WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX,
|
||||
CW_USEDEFAULT,CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,CW_USEDEFAULT,
|
||||
0, 0, instance, 0 );
|
||||
|
||||
{
|
||||
RECT r, c;
|
||||
GetWindowRect( w, &r );
|
||||
GetClientRect( w, &c );
|
||||
extrawindoww = ( r.right - r.left ) - ( c.right - c.left );
|
||||
extrawindowh = ( r.bottom - r.top ) - ( c.bottom - c.top );
|
||||
SetWindowPos( w, 0, 0, 0, bitmapw * curzoom + extrawindoww, bitmaph * curzoom + extrawindowh + 164, SWP_NOMOVE );
|
||||
}
|
||||
|
||||
ShowWindow( w, SW_SHOWNORMAL );
|
||||
SetTimer( w, 1, 250, 0 );
|
||||
|
||||
{
|
||||
BOOL ret;
|
||||
while( ( ret = GetMessage( &msg, w, 0, 0 ) ) != 0 )
|
||||
{
|
||||
if ( ret == -1 )
|
||||
break;
|
||||
TranslateMessage( &msg );
|
||||
DispatchMessage( &msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void retrain()
|
||||
{
|
||||
HANDLE threads[ 16 ];
|
||||
int chanind;
|
||||
|
||||
trainstart = GetTickCount();
|
||||
for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ )
|
||||
threads[ chanind ] = CreateThread( 0, 2048*1024, retrain_shim, (LPVOID)(size_t)chanind, 0, 0 );
|
||||
|
||||
draw_window();
|
||||
|
||||
for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ )
|
||||
{
|
||||
WaitForSingleObject( threads[ chanind ], INFINITE );
|
||||
CloseHandle( threads[ chanind ] );
|
||||
}
|
||||
|
||||
write_bitmap();
|
||||
|
||||
print_struct( retrain_weights, "retained_weights" );
|
||||
if ( windowstatus ) printf( "CANCELLED!\n" );
|
||||
}
|
||||
|
||||
static void info()
|
||||
{
|
||||
int findex;
|
||||
|
||||
// display info about each input file
|
||||
for( findex = 0 ; findex < numfileinfo ; findex++ )
|
||||
{
|
||||
int i, h,m,s;
|
||||
if ( findex ) printf( "\n" );
|
||||
printf( "Timing file: %s\n", fi[findex].filename );
|
||||
printf( "CPU type: %d %s\n", fi[findex].cpu, fi[findex].simd?(fi[findex].simd==2?"SIMD8":"SIMD4"):"Scalar" );
|
||||
h = fi[findex].milliseconds/3600000;
|
||||
m = (fi[findex].milliseconds-h*3600000)/60000;
|
||||
s = (fi[findex].milliseconds-h*3600000-m*60000)/1000;
|
||||
printf( "Total time in test: %dh %dm %ds Cycles/sec: %.f\n", h,m,s, 1000.0/fi[findex].scale_time );
|
||||
printf( "Each tile of samples is %dx%d, and is scaled by %dx%d.\n", fi[findex].dimensionx,fi[findex].dimensiony, fi[findex].outputscalex,fi[findex].outputscaley );
|
||||
printf( "So the x coords are: " );
|
||||
for( i=0; i < fi[findex].dimensionx ; i++ ) printf( "%d ",1+i*fi[findex].outputscalex );
|
||||
printf( "\n" );
|
||||
printf( "And the y coords are: " );
|
||||
for( i=0; i < fi[findex].dimensiony ; i++ ) printf( "%d ",1+i*fi[findex].outputscaley );
|
||||
printf( "\n" );
|
||||
printf( "There are %d channel counts and they are: ", fi[findex].numtypes );
|
||||
for( i=0; i < fi[findex].numtypes ; i++ ) printf( "%d ",fi[findex].effective[i] );
|
||||
printf( "\n" );
|
||||
printf( "There are %d input rect sizes and they are: ", fi[findex].numinputrects );
|
||||
for( i=0; i < fi[findex].numtypes ; i++ ) printf( "%dx%d ",fi[findex].inputrects[i*2],fi[findex].inputrects[i*2+1] );
|
||||
printf( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
static void current( int do_win, int do_bitmap )
|
||||
{
|
||||
int i, findex;
|
||||
|
||||
trainstart = GetTickCount();
|
||||
|
||||
// clear progress
|
||||
memset( windowranges, 0, sizeof( windowranges ) );
|
||||
// copy in appropriate weights
|
||||
memcpy( retrain_weights, stbir__compute_weights, sizeof( retrain_weights ) );
|
||||
|
||||
// build and print current errors and build current bitmap
|
||||
for( i = 0 ; i < fi[0].numtypes ; i++ )
|
||||
{
|
||||
double curerr[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
int curtot[STBIR_RESIZE_CLASSIFICATIONS];
|
||||
float (* weights)[4] = retrain_weights[i];
|
||||
|
||||
calc_errors( weights, curtot, curerr, i );
|
||||
if ( !do_bitmap )
|
||||
print_weights( weights, i, curtot, curerr );
|
||||
|
||||
for( findex = 0 ; findex < numfileinfo ; findex++ )
|
||||
build_bitmap( weights, i, findex );
|
||||
}
|
||||
|
||||
if ( do_win )
|
||||
draw_window();
|
||||
|
||||
if ( do_bitmap )
|
||||
write_bitmap();
|
||||
}
|
||||
|
||||
static void compare()
|
||||
{
|
||||
int i;
|
||||
|
||||
trainstart = GetTickCount();
|
||||
windowstatus = 2; // comp mode
|
||||
|
||||
// clear progress
|
||||
memset( windowranges, 0, sizeof( windowranges ) );
|
||||
|
||||
if ( ( fi[0].numtypes != fi[1].numtypes ) || ( fi[0].numinputrects != fi[1].numinputrects ) ||
|
||||
( fi[0].dimensionx != fi[1].dimensionx ) || ( fi[0].dimensiony != fi[1].dimensiony ) ||
|
||||
( fi[0].outputscalex != fi[1].outputscalex ) || ( fi[0].outputscaley != fi[1].outputscaley ) )
|
||||
{
|
||||
err:
|
||||
printf( "Timing files don't match.\n" );
|
||||
exit(5);
|
||||
}
|
||||
|
||||
for( i=0; i < fi[0].numtypes ; i++ )
|
||||
{
|
||||
if ( fi[0].effective[i] != fi[1].effective[i] ) goto err;
|
||||
if ( fi[0].inputrects[i*2] != fi[1].inputrects[i*2] ) goto err;
|
||||
if ( fi[0].inputrects[i*2+1] != fi[1].inputrects[i*2+1] ) goto err;
|
||||
}
|
||||
|
||||
alloc_bitmap( 1 );
|
||||
|
||||
for( i = 0 ; i < fi[0].numtypes ; i++ )
|
||||
{
|
||||
float (* weights)[4] = stbir__compute_weights[i];
|
||||
build_comp_bitmap( weights, i );
|
||||
}
|
||||
|
||||
draw_window();
|
||||
}
|
||||
|
||||
static void load_files( char ** args, int count )
|
||||
{
|
||||
int i;
|
||||
|
||||
if ( count == 0 )
|
||||
{
|
||||
printf( "No timing files listed!" );
|
||||
exit(3);
|
||||
}
|
||||
|
||||
for ( i = 0 ; i < count ; i++ )
|
||||
{
|
||||
if ( !use_timing_file( args[i], i ) )
|
||||
{
|
||||
printf( "Bad timing file %s\n", args[i] );
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
numfileinfo = count;
|
||||
}
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
int check;
|
||||
if ( argc < 3 )
|
||||
{
|
||||
err:
|
||||
printf( "vf_train retrain [timing_filenames....] - recalcs weights for all the files on the command line.\n");
|
||||
printf( "vf_train info [timing_filenames....] - shows info about each timing file.\n");
|
||||
printf( "vf_train check [timing_filenames...] - show results for the current weights for all files listed.\n");
|
||||
printf( "vf_train compare <timing file1> <timing file2> - compare two timing files (must only be two files and same resolution).\n");
|
||||
printf( "vf_train bitmap [timing_filenames...] - write out results.png, comparing against the current weights for all files listed.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
check = ( strcmp( argv[1], "check" ) == 0 );
|
||||
if ( ( check ) || ( strcmp( argv[1], "bitmap" ) == 0 ) )
|
||||
{
|
||||
load_files( argv + 2, argc - 2 );
|
||||
alloc_bitmap( numfileinfo );
|
||||
current( check, !check );
|
||||
}
|
||||
else if ( strcmp( argv[1], "info" ) == 0 )
|
||||
{
|
||||
load_files( argv + 2, argc - 2 );
|
||||
info();
|
||||
}
|
||||
else if ( strcmp( argv[1], "compare" ) == 0 )
|
||||
{
|
||||
if ( argc != 4 )
|
||||
{
|
||||
printf( "You must specify two files to compare.\n" );
|
||||
exit(4);
|
||||
}
|
||||
|
||||
load_files( argv + 2, argc - 2 );
|
||||
compare();
|
||||
}
|
||||
else if ( strcmp( argv[1], "retrain" ) == 0 )
|
||||
{
|
||||
load_files( argv + 2, argc - 2 );
|
||||
alloc_bitmap( numfileinfo );
|
||||
retrain();
|
||||
}
|
||||
else
|
||||
{
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
13
external/stb/stb/tests/resample_test.cpp
vendored
13
external/stb/stb/tests/resample_test.cpp
vendored
@ -64,7 +64,7 @@ void stbir_progress(float p)
|
||||
#define STBIR_PROGRESS_REPORT stbir_progress
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#define STB_IMAGE_RESIZE_STATIC
|
||||
#include "stb_image_resize.h"
|
||||
#include "stb_image_resize2.h"
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
@ -143,7 +143,7 @@ void resizer(int argc, char **argv)
|
||||
out_h = h*3;
|
||||
output_pixels = (unsigned char*) malloc(out_w*out_h*n);
|
||||
//stbir_resize_uint8_srgb(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, n, -1,0);
|
||||
stbir_resize_uint8(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, n);
|
||||
stbir_resize_uint8_linear(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, (stbir_pixel_layout) n);
|
||||
stbi_write_png("output.png", out_w, out_h, n, output_pixels, 0);
|
||||
exit(0);
|
||||
}
|
||||
@ -171,9 +171,9 @@ void performance(int argc, char **argv)
|
||||
output_pixels = (unsigned char*) malloc(out_w*out_h*n);
|
||||
for (i=0; i < count; ++i)
|
||||
if (srgb)
|
||||
stbir_resize_uint8_srgb(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, n,-1,0);
|
||||
stbir_resize_uint8_srgb(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, (stbir_pixel_layout) n);
|
||||
else
|
||||
stbir_resize(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, STBIR_TYPE_UINT8, n,-1, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, STBIR_COLORSPACE_LINEAR, NULL);
|
||||
stbir_resize_uint8_linear(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, (stbir_pixel_layout) n);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@ -188,6 +188,7 @@ int main(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void resize_image(const char* filename, float width_percent, float height_percent, stbir_filter filter, stbir_edge edge, stbir_colorspace colorspace, const char* output_filename)
|
||||
{
|
||||
int w, h, n;
|
||||
@ -1120,3 +1121,7 @@ void test_suite(int argc, char **argv)
|
||||
resize_image("gamma_2.2.jpg", .5f, .5f, STBIR_FILTER_CATMULLROM, STBIR_EDGE_REFLECT, STBIR_COLORSPACE_SRGB, "test-output/gamma_2.2.jpg");
|
||||
resize_image("gamma_dalai_lama_gray.jpg", .5f, .5f, STBIR_FILTER_CATMULLROM, STBIR_EDGE_REFLECT, STBIR_COLORSPACE_SRGB, "test-output/gamma_dalai_lama_gray.jpg");
|
||||
}
|
||||
#endif
|
||||
void test_suite(int argc, char **argv)
|
||||
{
|
||||
}
|
||||
|
2
external/stb/stb/tests/resize.dsp
vendored
2
external/stb/stb/tests/resize.dsp
vendored
@ -88,7 +88,7 @@ SOURCE=.\resample_test.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\stb_image_resize.h
|
||||
SOURCE=..\stb_image_resize2.h
|
||||
# End Source File
|
||||
# End Target
|
||||
# End Project
|
||||
|
4
external/stb/stb/tests/stb.dsp
vendored
4
external/stb/stb/tests/stb.dsp
vendored
@ -130,10 +130,6 @@ SOURCE=..\stb_image.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\stb_image_resize.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\stb_image_write.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
5
external/stb/stb/tests/test_c_compilation.c
vendored
5
external/stb/stb/tests/test_c_compilation.c
vendored
@ -1,3 +1,6 @@
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "stb_image_resize2.h"
|
||||
|
||||
#define STB_SPRINTF_IMPLEMENTATION
|
||||
#include "stb_sprintf.h"
|
||||
|
||||
@ -7,7 +10,6 @@
|
||||
#define STB_DIVIDE_IMPLEMENTATION
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_HERRINGBONE_WANG_TILE_IMEPLEMENTATIOn
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#define STB_VOXEL_RENDER_IMPLEMENTATION
|
||||
#define STB_EASY_FONT_IMPLEMENTATION
|
||||
@ -20,7 +22,6 @@
|
||||
#include "stb_perlin.h"
|
||||
#include "stb_c_lexer.h"
|
||||
#include "stb_divide.h"
|
||||
#include "stb_image_resize.h"
|
||||
#include "stb_rect_pack.h"
|
||||
#include "stb_dxt.h"
|
||||
#include "stb_include.h"
|
||||
|
@ -70,7 +70,7 @@ void my_free(void *) { }
|
||||
#include "stb_leakcheck.h"
|
||||
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "stb_image_resize.h"
|
||||
#include "stb_image_resize2.h"
|
||||
|
||||
//#include "stretchy_buffer.h" // deprecating
|
||||
|
||||
|
8
external/stb/stb/tools/README.header.md
vendored
8
external/stb/stb/tools/README.header.md
vendored
@ -3,16 +3,18 @@ stb
|
||||
|
||||
single-file public domain (or MIT licensed) libraries for C/C++
|
||||
|
||||
# This project discusses security-relevant bugs in public in Github Issues and Pull Requests, and it may take significant time for security fixes to be implemented or merged. If this poses an unreasonable risk to your project, do not use stb libraries.
|
||||
|
||||
Noteworthy:
|
||||
|
||||
* image loader: [stb_image.h](stb_image.h)
|
||||
* image writer: [stb_image_write.h](stb_image_write.h)
|
||||
* image resizer: [stb_image_resize.h](stb_image_resize.h)
|
||||
* image resizer: [stb_image_resize2.h](stb_image_resize2.h)
|
||||
* font text rasterizer: [stb_truetype.h](stb_truetype.h)
|
||||
* typesafe containers: [stb_ds.h](stb_ds.h)
|
||||
|
||||
Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, stb_image_resize
|
||||
by Jorge L. "VinoBS" Rodriguez, and stb_sprintf by Jeff Roberts.
|
||||
Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, original stb_image_resize
|
||||
by Jorge L. "VinoBS" Rodriguez, and stb_image_resize2 and stb_sprintf by Jeff Roberts.
|
||||
|
||||
<a name="stb_libs"></a>
|
||||
|
||||
|
2
external/stb/stb/tools/README.list
vendored
2
external/stb/stb/tools/README.list
vendored
@ -3,7 +3,7 @@ stb_hexwave.h | audio | audio waveform synthesizer
|
||||
stb_image.h | graphics | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC
|
||||
stb_truetype.h | graphics | parse, decode, and rasterize characters from truetype fonts
|
||||
stb_image_write.h | graphics | image writing to disk: PNG, TGA, BMP
|
||||
stb_image_resize.h | graphics | resize images larger/smaller with good quality
|
||||
stb_image_resize2.h | graphics | resize images larger/smaller with good quality
|
||||
stb_rect_pack.h | graphics | simple 2D rectangle packer with decent quality
|
||||
stb_perlin.h | graphics | perlin's revised simplex noise w/ different seeds
|
||||
stb_ds.h | utility | typesafe dynamic array and hash tables for C, will compile in C++
|
||||
|
BIN
res/tomato_screenshot_group_bot_text_23-02-2024.png
Normal file
BIN
res/tomato_screenshot_group_bot_text_23-02-2024.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
@ -25,6 +25,8 @@ add_executable(tomato
|
||||
./image_loader_stb.cpp
|
||||
./image_loader_webp.hpp
|
||||
./image_loader_webp.cpp
|
||||
./image_loader_qoi.hpp
|
||||
./image_loader_qoi.cpp
|
||||
|
||||
./texture_uploader.hpp
|
||||
./sdlrenderer_texture_uploader.hpp
|
||||
@ -61,6 +63,9 @@ add_executable(tomato
|
||||
./tox_dht_cap_histo.hpp
|
||||
./tox_dht_cap_histo.cpp
|
||||
|
||||
./tox_friend_faux_offline_messaging.hpp
|
||||
./tox_friend_faux_offline_messaging.cpp
|
||||
|
||||
./chat_gui4.hpp
|
||||
./chat_gui4.cpp
|
||||
)
|
||||
@ -87,5 +92,6 @@ target_link_libraries(tomato PUBLIC
|
||||
stb_image_write
|
||||
webpdemux
|
||||
libwebpmux # the f why (needed for anim encode)
|
||||
qoi
|
||||
)
|
||||
|
||||
|
@ -17,10 +17,8 @@
|
||||
|
||||
#include "./media_meta_info_loader.hpp"
|
||||
#include "./sdl_clipboard_utils.hpp"
|
||||
#include "SDL_clipboard.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <cstdio>
|
||||
#include <chrono>
|
||||
@ -28,10 +26,7 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace Components {
|
||||
|
||||
@ -42,7 +37,7 @@ namespace Components {
|
||||
|
||||
} // Components
|
||||
|
||||
static float lerp(float a, float b, float t) {
|
||||
static constexpr float lerp(float a, float b, float t) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
@ -72,28 +67,78 @@ static std::string file_path_url_escape(const std::string&& value) {
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
const void* clipboard_callback(void* userdata, const char* mime_type, size_t* size) {
|
||||
if (mime_type == nullptr) {
|
||||
// cleared or new data is set
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (userdata == nullptr) {
|
||||
// error
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* cg = static_cast<ChatGui4*>(userdata);
|
||||
std::lock_guard lg{cg->_set_clipboard_data_mutex};
|
||||
if (!cg->_set_clipboard_data.count(mime_type)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& sh_vec = cg->_set_clipboard_data.at(mime_type);
|
||||
if (!static_cast<bool>(sh_vec)) {
|
||||
// error, empty shared pointer
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*size = sh_vec->size();
|
||||
|
||||
return sh_vec->data();
|
||||
}
|
||||
|
||||
void ChatGui4::setClipboardData(std::vector<std::string> mime_types, std::shared_ptr<std::vector<uint8_t>>&& data) {
|
||||
if (!static_cast<bool>(data)) {
|
||||
std::cerr << "CG error: tried to set clipboard with empty shp\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->empty()) {
|
||||
std::cerr << "CG error: tried to set clipboard with empty data\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<const char*> tmp_mimetype_list;
|
||||
|
||||
std::lock_guard lg{_set_clipboard_data_mutex};
|
||||
for (const auto& mime_type : mime_types) {
|
||||
tmp_mimetype_list.push_back(mime_type.data());
|
||||
_set_clipboard_data[mime_type] = data;
|
||||
}
|
||||
|
||||
SDL_SetClipboardData(clipboard_callback, nullptr, this, tmp_mimetype_list.data(), tmp_mimetype_list.size());
|
||||
}
|
||||
|
||||
ChatGui4::ChatGui4(
|
||||
ConfigModelI& conf,
|
||||
RegistryMessageModel& rmm,
|
||||
Contact3Registry& cr,
|
||||
TextureUploaderI& tu
|
||||
) : _conf(conf), _rmm(rmm), _cr(cr), _tal(_cr), _contact_tc(_tal, tu), _msg_tc(_mil, tu), _sip(tu) {
|
||||
TextureUploaderI& tu,
|
||||
ContactTextureCache& contact_tc,
|
||||
MessageTextureCache& msg_tc
|
||||
) : _conf(conf), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _sip(tu) {
|
||||
}
|
||||
|
||||
void ChatGui4::render(float time_delta) {
|
||||
if (!_cr.storage<Contact::Components::TagAvatarInvalidate>().empty()) { // handle force-reloads for avatars
|
||||
std::vector<Contact3> to_purge;
|
||||
_cr.view<Contact::Components::TagAvatarInvalidate>().each([&to_purge](const Contact3 c) {
|
||||
to_purge.push_back(c);
|
||||
});
|
||||
_cr.remove<Contact::Components::TagAvatarInvalidate>(to_purge.cbegin(), to_purge.cend());
|
||||
_contact_tc.invalidate(to_purge);
|
||||
}
|
||||
// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
|
||||
// it might unload textures, so it needs to be done before rendering
|
||||
_contact_tc.update();
|
||||
_msg_tc.update();
|
||||
ChatGui4::~ChatGui4(void) {
|
||||
// TODO: this is bs
|
||||
SDL_ClearClipboardData();
|
||||
|
||||
// this might be better, need to see if this works (docs needs improving)
|
||||
//for (const auto& [k, _] : _set_clipboard_data) {
|
||||
//const auto* tmp_mime_type = k.c_str();
|
||||
//SDL_SetClipboardData(nullptr, nullptr, nullptr, &tmp_mime_type, 1);
|
||||
//}
|
||||
}
|
||||
|
||||
float ChatGui4::render(float time_delta) {
|
||||
_fss.render();
|
||||
_sip.render(time_delta);
|
||||
|
||||
@ -279,6 +324,7 @@ void ChatGui4::render(float time_delta) {
|
||||
//tmp_view.use<Message::Components::Timestamp>();
|
||||
//tmp_view.each([&](const Message3 e, Message::Components::ContactFrom& c_from, Message::Components::ContactTo& c_to, Message::Components::Timestamp ts
|
||||
//) {
|
||||
uint64_t prev_ts {0};
|
||||
auto tmp_view = msg_reg.view<Message::Components::Timestamp>();
|
||||
for (auto view_it = tmp_view.rbegin(), view_last = tmp_view.rend(); view_it != view_last; view_it++) {
|
||||
const Message3 e = *view_it;
|
||||
@ -296,6 +342,34 @@ void ChatGui4::render(float time_delta) {
|
||||
// TODO: why?
|
||||
ImGui::TableNextRow(0, TEXT_BASE_HEIGHT);
|
||||
|
||||
{ // check if date changed
|
||||
// TODO: find defined ways of casting to time_t
|
||||
std::time_t prev = prev_ts / 1000;
|
||||
std::time_t next = ts.ts / 1000;
|
||||
std::tm prev_tm = *std::localtime(&prev);
|
||||
std::tm next_tm = *std::localtime(&next);
|
||||
if (
|
||||
prev_tm.tm_yday != next_tm.tm_yday ||
|
||||
prev_tm.tm_year != next_tm.tm_year // making sure
|
||||
) {
|
||||
// name
|
||||
if (ImGui::TableNextColumn()) {
|
||||
//ImGui::TextDisabled("---");
|
||||
}
|
||||
// msg
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextDisabled("DATE CHANGED from %d.%d.%d to %d.%d.%d",
|
||||
1900+prev_tm.tm_year, 1+prev_tm.tm_mon, prev_tm.tm_mday,
|
||||
1900+next_tm.tm_year, 1+next_tm.tm_mon, next_tm.tm_mday
|
||||
);
|
||||
}
|
||||
ImGui::TableNextRow(0, TEXT_BASE_HEIGHT);
|
||||
}
|
||||
|
||||
prev_ts = ts.ts;
|
||||
}
|
||||
|
||||
|
||||
ImGui::PushID(entt::to_integral(e));
|
||||
|
||||
// name
|
||||
@ -514,7 +588,7 @@ void ChatGui4::render(float time_delta) {
|
||||
_fss.requestFile(
|
||||
[](const auto& path) -> bool { return std::filesystem::is_regular_file(path); },
|
||||
[this](const auto& path){
|
||||
_rmm.sendFilePath(*_selected_contact, path.filename().u8string(), path.u8string());
|
||||
_rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string());
|
||||
},
|
||||
[](){}
|
||||
);
|
||||
@ -539,6 +613,7 @@ void ChatGui4::render(float time_delta) {
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/bmp",
|
||||
"image/qoi",
|
||||
};
|
||||
|
||||
for (const char* mime_type : image_mime_types) {
|
||||
@ -571,13 +646,12 @@ void ChatGui4::render(float time_delta) {
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
_contact_tc.workLoadQueue();
|
||||
_msg_tc.workLoadQueue();
|
||||
return 1000.f; // TODO: higher min fps?
|
||||
}
|
||||
|
||||
void ChatGui4::sendFilePath(const char* file_path) {
|
||||
if (_selected_contact && std::filesystem::is_regular_file(file_path)) {
|
||||
_rmm.sendFilePath(*_selected_contact, std::filesystem::path(file_path).filename().u8string(), file_path);
|
||||
_rmm.sendFilePath(*_selected_contact, std::filesystem::path(file_path).filename().generic_u8string(), file_path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -671,10 +745,10 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) {
|
||||
//for (const auto& c : _cr.view<entt::get_t<Contact::Components::TagBig>, entt::exclude_t<Contact::Components::RequestIncoming, Contact::Components::TagRequestOutgoing>>()) {
|
||||
for (const auto& c : _cr.view<Contact::Components::TagBig>()) {
|
||||
if (renderContactListContactSmall(c, false)) {
|
||||
//_rmm.sendFilePath(*_selected_contact, path.filename().u8string(), path.u8string());
|
||||
//_rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string());
|
||||
const auto& fil = reg.get<Message::Components::Transfer::FileInfoLocal>(e);
|
||||
for (const auto& path : fil.file_list) {
|
||||
_rmm.sendFilePath(c, std::filesystem::path{path}.filename().u8string(), path);
|
||||
_rmm.sendFilePath(c, std::filesystem::path{path}.filename().generic_u8string(), path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -693,7 +767,11 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) {
|
||||
) {
|
||||
if (ImGui::Button("save to")) {
|
||||
_fss.requestFile(
|
||||
[](const auto& path) -> bool { return std::filesystem::is_directory(path); },
|
||||
[](std::filesystem::path& path) -> bool {
|
||||
// remove file path
|
||||
path.remove_filename();
|
||||
return std::filesystem::is_directory(path);
|
||||
},
|
||||
[this, ®, e](const auto& path) {
|
||||
if (reg.valid(e)) { // still valid
|
||||
// TODO: trim file?
|
||||
@ -761,10 +839,19 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) {
|
||||
const auto& local_info = reg.get<Message::Components::Transfer::FileInfoLocal>(e);
|
||||
if (local_info.file_list.size() > i && ImGui::BeginPopupContextItem("##file_c")) {
|
||||
if (ImGui::MenuItem("open")) {
|
||||
std::string url{"file://" + file_path_url_escape(std::filesystem::canonical(local_info.file_list.at(i)).u8string())};
|
||||
const std::string url{"file://" + file_path_url_escape(std::filesystem::canonical(local_info.file_list.at(i)).generic_u8string())};
|
||||
std::cout << "opening file '" << url << "'\n";
|
||||
SDL_OpenURL(url.c_str());
|
||||
}
|
||||
if (ImGui::MenuItem("copy file")) {
|
||||
const std::string url{"file://" + file_path_url_escape(std::filesystem::canonical(local_info.file_list.at(i)).generic_u8string())};
|
||||
//ImGui::SetClipboardText(url.c_str());
|
||||
setClipboardData({"text/uri-list", "text/x-moz-url"}, std::make_shared<std::vector<uint8_t>>(url.begin(), url.end()));
|
||||
}
|
||||
if (ImGui::MenuItem("copy filepath")) {
|
||||
const auto file_path = std::filesystem::canonical(local_info.file_list.at(i)).u8string(); //TODO: use generic over native?
|
||||
ImGui::SetClipboardText(file_path.c_str());
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
@ -1065,7 +1152,7 @@ void ChatGui4::pasteFile(const char* mime_type) {
|
||||
std::ofstream(tmp_file_path, std::ios_base::out | std::ios_base::binary)
|
||||
.write(reinterpret_cast<const char*>(img_data.data()), img_data.size());
|
||||
|
||||
_rmm.sendFilePath(*_selected_contact, tmp_file_name.str(), tmp_file_path.u8string());
|
||||
_rmm.sendFilePath(*_selected_contact, tmp_file_name.str(), tmp_file_path.generic_u8string());
|
||||
},
|
||||
[](){}
|
||||
);
|
||||
|
@ -10,18 +10,24 @@
|
||||
#include "./file_selector.hpp"
|
||||
#include "./send_image_popup.hpp"
|
||||
|
||||
#include <entt/container/dense_map.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
using ContactTextureCache = TextureCache<void*, Contact3, ToxAvatarLoader>;
|
||||
using MessageTextureCache = TextureCache<void*, Message3Handle, MessageImageLoader>;
|
||||
|
||||
class ChatGui4 {
|
||||
ConfigModelI& _conf;
|
||||
RegistryMessageModel& _rmm;
|
||||
Contact3Registry& _cr;
|
||||
|
||||
ToxAvatarLoader _tal;
|
||||
TextureCache<void*, Contact3, ToxAvatarLoader> _contact_tc;
|
||||
MessageImageLoader _mil;
|
||||
TextureCache<void*, Message3Handle, MessageImageLoader> _msg_tc;
|
||||
ContactTextureCache& _contact_tc;
|
||||
MessageTextureCache& _msg_tc;
|
||||
|
||||
FileSelector _fss;
|
||||
SendImagePopup _sip;
|
||||
@ -37,16 +43,25 @@ class ChatGui4 {
|
||||
float TEXT_BASE_WIDTH {1};
|
||||
float TEXT_BASE_HEIGHT {1};
|
||||
|
||||
// mimetype -> data
|
||||
entt::dense_map<std::string, std::shared_ptr<std::vector<uint8_t>>> _set_clipboard_data;
|
||||
std::mutex _set_clipboard_data_mutex; // might be called out of order
|
||||
friend const void* clipboard_callback(void* userdata, const char* mime_type, size_t* size);
|
||||
void setClipboardData(std::vector<std::string> mime_types, std::shared_ptr<std::vector<uint8_t>>&& data);
|
||||
|
||||
public:
|
||||
ChatGui4(
|
||||
ConfigModelI& conf,
|
||||
RegistryMessageModel& rmm,
|
||||
Contact3Registry& cr,
|
||||
TextureUploaderI& tu
|
||||
TextureUploaderI& tu,
|
||||
ContactTextureCache& contact_tc,
|
||||
MessageTextureCache& msg_tc
|
||||
);
|
||||
~ChatGui4(void);
|
||||
|
||||
public:
|
||||
void render(float time_delta);
|
||||
float render(float time_delta);
|
||||
|
||||
public:
|
||||
bool any_unread {false};
|
||||
|
@ -18,7 +18,7 @@ FileSelector::FileSelector(void) {
|
||||
}
|
||||
|
||||
void FileSelector::requestFile(
|
||||
std::function<bool(const std::filesystem::path& path)>&& is_valid,
|
||||
std::function<bool(std::filesystem::path& path)>&& is_valid,
|
||||
std::function<void(const std::filesystem::path& path)>&& on_choose,
|
||||
std::function<void(void)>&& on_cancel
|
||||
) {
|
||||
@ -47,7 +47,7 @@ void FileSelector::render(void) {
|
||||
std::filesystem::path current_path = _current_file_path;
|
||||
current_path.remove_filename();
|
||||
|
||||
ImGui::Text("path: %s", _current_file_path.u8string().c_str());
|
||||
ImGui::Text("path: %s", _current_file_path.generic_u8string().c_str());
|
||||
|
||||
// begin table with selectables
|
||||
constexpr ImGuiTableFlags table_flags =
|
||||
@ -77,7 +77,9 @@ void FileSelector::render(void) {
|
||||
if (current_path.has_parent_path()) {
|
||||
if (ImGui::TableNextColumn()) {
|
||||
if (ImGui::Selectable("D##..", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
|
||||
_current_file_path = _current_file_path.parent_path();
|
||||
// the first "parent_path()" only removes the filename and the ending "/"
|
||||
_current_file_path = _current_file_path.parent_path().parent_path() / "";
|
||||
//_current_file_path = _current_file_path.remove_filename().parent_path() / "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +177,7 @@ void FileSelector::render(void) {
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted((dir_entry.path().filename().u8string() + "/").c_str());
|
||||
ImGui::TextUnformatted((dir_entry.path().filename().generic_u8string() + "/").c_str());
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
@ -206,7 +208,7 @@ void FileSelector::render(void) {
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted(dir_entry.path().filename().u8string().c_str());
|
||||
ImGui::TextUnformatted(dir_entry.path().filename().generic_u8string().c_str());
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
|
@ -8,7 +8,7 @@ struct FileSelector {
|
||||
|
||||
bool _open_popup {false};
|
||||
|
||||
std::function<bool(const std::filesystem::path& path)> _is_valid = [](auto){ return true; };
|
||||
std::function<bool(std::filesystem::path& path)> _is_valid = [](auto){ return true; };
|
||||
std::function<void(const std::filesystem::path& path)> _on_choose = [](auto){};
|
||||
std::function<void(void)> _on_cancel = [](){};
|
||||
|
||||
@ -18,8 +18,9 @@ struct FileSelector {
|
||||
FileSelector(void);
|
||||
|
||||
// TODO: supply hints
|
||||
// HACK: until we supply hints, is_valid can modify
|
||||
void requestFile(
|
||||
std::function<bool(const std::filesystem::path& path)>&& is_valid,
|
||||
std::function<bool(std::filesystem::path& path)>&& is_valid,
|
||||
std::function<void(const std::filesystem::path& path)>&& on_choose,
|
||||
std::function<void(void)>&& on_cancel
|
||||
);
|
||||
|
72
src/fragment_store/README.md
Normal file
72
src/fragment_store/README.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Fragment Store
|
||||
|
||||
Fragments are are pieces of information split into Metadata and Data.
|
||||
They can be stored seperated or together.
|
||||
They can be used as a Transport protocol/logic too.
|
||||
|
||||
# Store types
|
||||
|
||||
### Object Store
|
||||
|
||||
Fragment files are stored with the first 2 hex chars as sub folders:
|
||||
eg:
|
||||
`objects/` (object store root)
|
||||
- `5f/` (first 2hex subfolder)
|
||||
- `4fffffff` (the fragment file without the first 2 hexchars)
|
||||
|
||||
### Split Object Store
|
||||
|
||||
Same as Object Store, but medadata and data stored in seperate files.
|
||||
Metadata files have the `.meta` suffix. They also have a filetype specific suffix, like `.json`, `.msgpack` etc.
|
||||
|
||||
### Memory Store
|
||||
|
||||
Just keeps the Fragments in memory.
|
||||
|
||||
# File formats
|
||||
|
||||
Files can be compressed and encrypted. Since compression needs the data structure to funcion, it is applied before it is encrypted.
|
||||
|
||||
### Text Json
|
||||
|
||||
Text json only makes sense for metadata if it's neither compressed nor encrypted. (otherwise its binary on disk anyway, so why waste bytes).
|
||||
Since the content of data is not looked at, nothing stops you from using text json and ecrypt it, but atleast basic compression is advised.
|
||||
|
||||
A Metadata json object has the following keys:
|
||||
- `enc` (uint) Encryption type of the data, if any
|
||||
- `comp` (uint) Compression type of the data, if any
|
||||
- `metadata` (obj) the
|
||||
|
||||
## Binary file headers
|
||||
|
||||
### Split Metadata
|
||||
|
||||
file magic bytes `SOLMET` (6 bytes)
|
||||
|
||||
1 byte encryption type (`0x00` is none)
|
||||
|
||||
1 byte compression type (`0x00` is none)
|
||||
|
||||
...metadata here...
|
||||
|
||||
note that the encryption and compression are for the metadata only.
|
||||
The metadata itself contains encryption and compression info about the data.
|
||||
|
||||
### Split Data
|
||||
|
||||
(none) all the data is in the metadata file.
|
||||
This is mostly to allow direct storage for files in the Fragment store without excessive duplication.
|
||||
Keep in mind to not use the actual file name as the data/meta file name.
|
||||
|
||||
### Single fragment
|
||||
|
||||
file magic bytes `SOLFIL` (6 bytes)
|
||||
|
||||
1 byte encryption type (`0x00` is none)
|
||||
|
||||
1 byte compression type (`0x00` is none)
|
||||
|
||||
...metadata here...
|
||||
|
||||
...data here...
|
||||
|
91
src/image_loader_qoi.cpp
Normal file
91
src/image_loader_qoi.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include "./image_loader_qoi.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <qoi/qoi.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
ImageLoaderQOI::ImageInfo ImageLoaderQOI::loadInfoFromMemory(const uint8_t* data, uint64_t data_size) {
|
||||
ImageInfo res;
|
||||
|
||||
qoi_desc desc;
|
||||
// TODO: only read the header
|
||||
auto* ret = qoi_decode(data, data_size, &desc, 4);
|
||||
if (ret == nullptr) {
|
||||
return res;
|
||||
}
|
||||
free(ret);
|
||||
|
||||
res.width = desc.width;
|
||||
res.height = desc.height;
|
||||
//desc.colorspace;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ImageLoaderQOI::ImageResult ImageLoaderQOI::loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) {
|
||||
ImageResult res;
|
||||
|
||||
qoi_desc desc;
|
||||
|
||||
uint8_t* img_data = static_cast<uint8_t*>(
|
||||
qoi_decode(data, data_size, &desc, 4)
|
||||
);
|
||||
if (img_data == nullptr) {
|
||||
// not readable
|
||||
return res;
|
||||
}
|
||||
|
||||
res.width = desc.width;
|
||||
res.height = desc.height;
|
||||
|
||||
auto& new_frame = res.frames.emplace_back();
|
||||
new_frame.ms = 0;
|
||||
new_frame.data.insert(new_frame.data.cbegin(), img_data, img_data+(desc.width*desc.height*4));
|
||||
|
||||
free(img_data);
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& input_image, const std::map<std::string, float>&) {
|
||||
if (input_image.frames.empty()) {
|
||||
std::cerr << "IEQOI error: empty image\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (input_image.frames.size() > 1) {
|
||||
std::cerr << "IEQOI warning: image with animation, only first frame will be encoded!\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: look into RDO (eg https://github.com/richgel999/rdopng)
|
||||
//int png_compression_level = 8;
|
||||
//if (extra_options.count("png_compression_level")) {
|
||||
//png_compression_level = extra_options.at("png_compression_level");
|
||||
//}
|
||||
|
||||
qoi_desc desc;
|
||||
desc.width = input_image.width;
|
||||
desc.height = input_image.height;
|
||||
desc.channels = 4;
|
||||
desc.colorspace = QOI_SRGB; // TODO: decide
|
||||
|
||||
int out_len {0};
|
||||
uint8_t* enc_data = static_cast<uint8_t*>(qoi_encode(
|
||||
input_image.frames.front().data.data(),
|
||||
&desc,
|
||||
&out_len
|
||||
));
|
||||
|
||||
if (enc_data == nullptr) {
|
||||
std::cerr << "IEQOI error: qoi_encode failed!\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> new_data(enc_data, enc_data+out_len);
|
||||
|
||||
free(enc_data); // TODO: a streaming encoder would be better
|
||||
|
||||
return new_data;
|
||||
}
|
||||
|
13
src/image_loader_qoi.hpp
Normal file
13
src/image_loader_qoi.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "./image_loader.hpp"
|
||||
|
||||
struct ImageLoaderQOI : public ImageLoaderI {
|
||||
ImageInfo loadInfoFromMemory(const uint8_t* data, uint64_t data_size) override;
|
||||
ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) override;
|
||||
};
|
||||
|
||||
struct ImageEncoderQOI : public ImageEncoderI {
|
||||
std::vector<uint8_t> encodeToMemoryRGBA(const ImageResult& input_image, const std::map<std::string, float>& extra_options = {}) override;
|
||||
};
|
||||
|
10
src/main.cpp
10
src/main.cpp
@ -178,9 +178,13 @@ int main(int argc, char** argv) {
|
||||
//)
|
||||
//));
|
||||
|
||||
const float min_delay = std::min<float>(
|
||||
screen->nextTick() - time_delta_tick,
|
||||
screen->nextRender() - time_delta_render
|
||||
const float min_delay =
|
||||
std::min<float>(
|
||||
std::min<float>(
|
||||
screen->nextTick() - time_delta_tick,
|
||||
screen->nextRender() - time_delta_render
|
||||
),
|
||||
0.25f // dont sleep too long
|
||||
) * 1000.f;
|
||||
|
||||
if (min_delay > 0.f) {
|
||||
|
@ -1,10 +1,13 @@
|
||||
#include "./main_screen.hpp"
|
||||
|
||||
#include <solanaceae/contact/components.hpp>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins) :
|
||||
renderer(renderer_),
|
||||
@ -16,10 +19,15 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri
|
||||
tcm(cr, tc, tc),
|
||||
tmm(rmm, cr, tcm, tc, tc),
|
||||
ttm(rmm, cr, tcm, tc, tc),
|
||||
tffom(cr, rmm, tcm, tc, tc),
|
||||
mmil(rmm),
|
||||
tam(rmm, cr, conf),
|
||||
sdlrtu(renderer_),
|
||||
cg(conf, rmm, cr, sdlrtu),
|
||||
tal(cr),
|
||||
contact_tc(tal, sdlrtu),
|
||||
mil(),
|
||||
msg_tc(mil, sdlrtu),
|
||||
cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc),
|
||||
sw(conf),
|
||||
tuiu(tc, conf),
|
||||
tdch(tpi)
|
||||
@ -166,7 +174,22 @@ Screen* MainScreen::render(float time_delta, bool&) {
|
||||
|
||||
const float pm_interval = pm.render(time_delta); // render
|
||||
|
||||
cg.render(time_delta); // render
|
||||
// TODO: move this somewhere else!!!
|
||||
// needs both tal and tc <.<
|
||||
if (!cr.storage<Contact::Components::TagAvatarInvalidate>().empty()) { // handle force-reloads for avatars
|
||||
std::vector<Contact3> to_purge;
|
||||
cr.view<Contact::Components::TagAvatarInvalidate>().each([&to_purge](const Contact3 c) {
|
||||
to_purge.push_back(c);
|
||||
});
|
||||
cr.remove<Contact::Components::TagAvatarInvalidate>(to_purge.cbegin(), to_purge.cend());
|
||||
contact_tc.invalidate(to_purge);
|
||||
}
|
||||
// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
|
||||
// it might unload textures, so it needs to be done before rendering
|
||||
const float ctc_interval = contact_tc.update();
|
||||
const float msgtc_interval = msg_tc.update();
|
||||
|
||||
const float cg_interval = cg.render(time_delta); // render
|
||||
sw.render(); // render
|
||||
tuiu.render(); // render
|
||||
tdch.render(); // render
|
||||
@ -191,6 +214,12 @@ Screen* MainScreen::render(float time_delta, bool&) {
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Settings")) {
|
||||
if (ImGui::MenuItem("ImGui Style Editor")) {
|
||||
_show_tool_style_editor = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
@ -198,25 +227,147 @@ Screen* MainScreen::render(float time_delta, bool&) {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (_show_tool_style_editor) {
|
||||
if (ImGui::Begin("Dear ImGui Style Editor", &_show_tool_style_editor)) {
|
||||
ImGui::ShowStyleEditor();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if constexpr (false) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
||||
if (
|
||||
_fps_perf_mode > 1 // TODO: magic
|
||||
) {
|
||||
// powersave forces 250ms
|
||||
_render_interval = 1.f/4.f;
|
||||
} else if (
|
||||
_time_since_event > 1.f && ( // 1sec cool down
|
||||
_fps_perf_mode == 1 || // TODO: magic
|
||||
_window_hidden
|
||||
float tc_unfinished_queue_interval;
|
||||
{ // load rendered but not loaded textures
|
||||
bool unfinished_work_queue = contact_tc.workLoadQueue();
|
||||
unfinished_work_queue = unfinished_work_queue || msg_tc.workLoadQueue();
|
||||
|
||||
if (unfinished_work_queue) {
|
||||
tc_unfinished_queue_interval = 0.1f; // so we can get images loaded faster
|
||||
} else {
|
||||
tc_unfinished_queue_interval = 1.f; // TODO: higher min fps?
|
||||
}
|
||||
}
|
||||
|
||||
// calculate interval for next frame
|
||||
// normal:
|
||||
// - if < 1.5sec since last event
|
||||
// - min all and clamp(1/60, 1/1)
|
||||
// - if < 30sec since last event
|
||||
// - min all (anim + everything else) clamp(1/60, 1/1) (maybe less?)
|
||||
// - else
|
||||
// - min without anim and clamp(1/60, 1/1) (maybe more?)
|
||||
// reduced:
|
||||
// - if < 1sec since last event
|
||||
// - min all and clamp(1/60, 1/1)
|
||||
// - if < 10sec since last event
|
||||
// - min all (anim + everything else) clamp(1/10, 1/1)
|
||||
// - else
|
||||
// - min without anim and max clamp(1/10, 1/1)
|
||||
// powersave:
|
||||
// - if < 0sec since last event
|
||||
// - (ignored)
|
||||
// - if < 1sec since last event
|
||||
// - min all (anim + everything else) clamp(1/8, 1/1)
|
||||
// - else
|
||||
// - min without anim and clamp(1/1, 1/1)
|
||||
struct PerfProfileRender {
|
||||
float low_delay_window {1.5f};
|
||||
float low_delay_min {1.f/60.f};
|
||||
float low_delay_max {1.f/60.f};
|
||||
|
||||
float mid_delay_window {30.f};
|
||||
float mid_delay_min {1.f/60.f};
|
||||
float mid_delay_max {1.f/2.f};
|
||||
|
||||
// also when main window hidden
|
||||
float else_delay_min {1.f/60.f};
|
||||
float else_delay_max {1.f/2.f};
|
||||
};
|
||||
|
||||
const static PerfProfileRender normalPerfProfile{
|
||||
//1.5f, // low_delay_window
|
||||
//1.f/60.f, // low_delay_min
|
||||
//1.f/60.f, // low_delay_max
|
||||
|
||||
//30.f, // mid_delay_window
|
||||
//1.f/60.f, // mid_delay_min
|
||||
//1.f/2.f, // mid_delay_max
|
||||
|
||||
//1.f/60.f, // else_delay_min
|
||||
//1.f/2.f, // else_delay_max
|
||||
};
|
||||
const static PerfProfileRender reducedPerfProfile{
|
||||
1.f, // low_delay_window
|
||||
1.f/60.f, // low_delay_min
|
||||
1.f/30.f, // low_delay_max
|
||||
|
||||
10.f, // mid_delay_window
|
||||
1.f/10.f, // mid_delay_min
|
||||
1.f/4.f, // mid_delay_max
|
||||
|
||||
1.f/10.f, // else_delay_min
|
||||
1.f, // else_delay_max
|
||||
};
|
||||
// TODO: fix powersave by adjusting it in the events handler (make ppr member)
|
||||
const static PerfProfileRender powersavePerfProfile{
|
||||
// no window -> ignore first case
|
||||
0.f, // low_delay_window
|
||||
1.f, // low_delay_min
|
||||
1.f, // low_delay_max
|
||||
|
||||
1.f, // mid_delay_window
|
||||
1.f/8.f, // mid_delay_min
|
||||
1.f/4.f, // mid_delay_max
|
||||
|
||||
1.f, // else_delay_min
|
||||
1.f, // else_delay_max
|
||||
};
|
||||
|
||||
const PerfProfileRender& curr_profile =
|
||||
// TODO: magic
|
||||
_fps_perf_mode > 1
|
||||
? powersavePerfProfile
|
||||
: (
|
||||
_fps_perf_mode == 1
|
||||
? reducedPerfProfile
|
||||
: normalPerfProfile
|
||||
)
|
||||
) {
|
||||
_render_interval = std::min<float>(1.f/4.f, pm_interval);
|
||||
;
|
||||
|
||||
// min over non animations in all cases
|
||||
_render_interval = std::min<float>(pm_interval, cg_interval);
|
||||
_render_interval = std::min<float>(_render_interval, tc_unfinished_queue_interval);
|
||||
|
||||
// low delay time window
|
||||
if (!_window_hidden && _time_since_event < curr_profile.low_delay_window) {
|
||||
_render_interval = std::min<float>(_render_interval, ctc_interval);
|
||||
_render_interval = std::min<float>(_render_interval, msgtc_interval);
|
||||
|
||||
_render_interval = std::clamp(
|
||||
_render_interval,
|
||||
curr_profile.low_delay_min,
|
||||
curr_profile.low_delay_max
|
||||
);
|
||||
// mid delay time window
|
||||
} else if (!_window_hidden && _time_since_event < curr_profile.mid_delay_window) {
|
||||
_render_interval = std::min<float>(_render_interval, ctc_interval);
|
||||
_render_interval = std::min<float>(_render_interval, msgtc_interval);
|
||||
|
||||
_render_interval = std::clamp(
|
||||
_render_interval,
|
||||
curr_profile.mid_delay_min,
|
||||
curr_profile.mid_delay_max
|
||||
);
|
||||
// timed out or window hidden
|
||||
} else {
|
||||
_render_interval = std::min<float>(1.f/60.f, pm_interval);
|
||||
// no animation timing here
|
||||
_render_interval = std::clamp(
|
||||
_render_interval,
|
||||
curr_profile.else_delay_min,
|
||||
curr_profile.else_delay_max
|
||||
);
|
||||
}
|
||||
|
||||
_time_since_event += time_delta;
|
||||
@ -229,6 +380,8 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
|
||||
|
||||
tcm.iterate(time_delta); // compute
|
||||
|
||||
const float fo_interval = tffom.tick(time_delta);
|
||||
|
||||
tam.iterate(); // compute
|
||||
|
||||
const float pm_interval = pm.tick(time_delta); // compute
|
||||
@ -238,9 +391,17 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
|
||||
mts.iterate(); // compute
|
||||
|
||||
_min_tick_interval = std::min<float>(
|
||||
tc.toxIterationInterval()/1000.f,
|
||||
// HACK: pow by 1.6 to increase 50 -> ~500 (~522)
|
||||
// and it does not change 1
|
||||
std::pow(tc.toxIterationInterval(), 1.6f)/1000.f,
|
||||
pm_interval
|
||||
);
|
||||
_min_tick_interval = std::min<float>(
|
||||
_min_tick_interval,
|
||||
fo_interval
|
||||
);
|
||||
|
||||
//std::cout << "MS: min tick interval: " << _min_tick_interval << "\n";
|
||||
|
||||
switch (_compute_perf_mode) {
|
||||
// normal 1ms lower bound
|
||||
|
@ -21,10 +21,15 @@
|
||||
#include "./tox_avatar_manager.hpp"
|
||||
|
||||
#include "./sdlrenderer_texture_uploader.hpp"
|
||||
#include "./texture_cache.hpp"
|
||||
#include "./tox_avatar_loader.hpp"
|
||||
#include "./message_image_loader.hpp"
|
||||
|
||||
#include "./chat_gui4.hpp"
|
||||
#include "./settings_window.hpp"
|
||||
#include "./tox_ui_utils.hpp"
|
||||
#include "./tox_dht_cap_histo.hpp"
|
||||
#include "./tox_friend_faux_offline_messaging.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
@ -52,6 +57,7 @@ struct MainScreen final : public Screen {
|
||||
ToxContactModel2 tcm;
|
||||
ToxMessageManager tmm;
|
||||
ToxTransferManager ttm;
|
||||
ToxFriendFauxOfflineMessaging tffom;
|
||||
|
||||
MediaMetaInfoLoader mmil;
|
||||
ToxAvatarManager tam;
|
||||
@ -59,13 +65,20 @@ struct MainScreen final : public Screen {
|
||||
SDLRendererTextureUploader sdlrtu;
|
||||
//OpenGLTextureUploader ogltu;
|
||||
|
||||
ToxAvatarLoader tal;
|
||||
TextureCache<void*, Contact3, ToxAvatarLoader> contact_tc;
|
||||
MessageImageLoader mil;
|
||||
TextureCache<void*, Message3Handle, MessageImageLoader> msg_tc;
|
||||
|
||||
ChatGui4 cg;
|
||||
SettingsWindow sw;
|
||||
ToxUIUtils tuiu;
|
||||
ToxDHTCapHisto tdch;
|
||||
|
||||
bool _show_tool_style_editor {false};
|
||||
|
||||
bool _window_hidden {false};
|
||||
bool _window_hidden_ts {0};
|
||||
uint64_t _window_hidden_ts {0};
|
||||
float _time_since_event {0.f};
|
||||
|
||||
MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins);
|
||||
@ -81,7 +94,7 @@ struct MainScreen final : public Screen {
|
||||
// 0 - normal
|
||||
// 1 - reduced
|
||||
// 2 - power save
|
||||
int _fps_perf_mode {1};
|
||||
int _fps_perf_mode {0};
|
||||
// 0 - normal
|
||||
// 1 - power save
|
||||
int _compute_perf_mode {0};
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "./image_loader_webp.hpp"
|
||||
#include "./image_loader_sdl_bmp.hpp"
|
||||
#include "./image_loader_qoi.hpp"
|
||||
#include "./image_loader_stb.hpp"
|
||||
|
||||
#include <solanaceae/message3/components.hpp>
|
||||
@ -77,6 +78,7 @@ MediaMetaInfoLoader::MediaMetaInfoLoader(RegistryMessageModel& rmm) : _rmm(rmm)
|
||||
// HACK: make them be added externally?
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderQOI>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
|
||||
|
||||
_rmm.subscribe(this, RegistryMessageModel_Event::message_construct);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "./message_image_loader.hpp"
|
||||
|
||||
#include "./image_loader_sdl_bmp.hpp"
|
||||
#include "./image_loader_qoi.hpp"
|
||||
#include "./image_loader_stb.hpp"
|
||||
#include "./image_loader_webp.hpp"
|
||||
#include "./media_meta_info_loader.hpp"
|
||||
@ -19,6 +20,7 @@ uint64_t getTimeMS(void);
|
||||
|
||||
MessageImageLoader::MessageImageLoader(void) {
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderQOI>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
|
||||
}
|
||||
|
@ -17,16 +17,15 @@ uint64_t SDLRendererTextureUploader::uploadRGBA(const uint8_t* data, uint32_t wi
|
||||
);
|
||||
assert(surf); // TODO: add error reporting
|
||||
|
||||
// TODO: this touches global state, reset?
|
||||
if (filter == NEAREST) {
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
|
||||
} else if (filter == LINEAR) {
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
||||
}
|
||||
|
||||
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
|
||||
assert(tex); // TODO: add error reporting
|
||||
|
||||
if (filter == NEAREST) {
|
||||
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
|
||||
} else if (filter == LINEAR) {
|
||||
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
|
||||
}
|
||||
|
||||
SDL_DestroySurface(surf);
|
||||
|
||||
return reinterpret_cast<uint64_t>(tex);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "./image_loader_sdl_bmp.hpp"
|
||||
#include "./image_loader_stb.hpp"
|
||||
#include "./image_loader_webp.hpp"
|
||||
#include "./image_loader_qoi.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
@ -13,6 +14,7 @@ uint64_t getTimeMS(void);
|
||||
|
||||
SendImagePopup::SendImagePopup(TextureUploaderI& tu) : _tu(tu) {
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderQOI>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
|
||||
}
|
||||
@ -421,7 +423,7 @@ void SendImagePopup::render(float time_delta) {
|
||||
|
||||
if (compress) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Combo("##compression_type", ¤t_compressor, "webp\0jpeg\0png\n");
|
||||
ImGui::Combo("##compression_type", ¤t_compressor, "webp\0jpeg\0png\0qoi\0");
|
||||
|
||||
ImGui::Indent();
|
||||
// combo "webp""webp-lossless""png""jpg?"
|
||||
@ -486,6 +488,11 @@ void SendImagePopup::render(float time_delta) {
|
||||
if (!new_data.empty()) {
|
||||
_on_send(new_data, ".png");
|
||||
}
|
||||
} else if (current_compressor == 3) {
|
||||
new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {});;
|
||||
if (!new_data.empty()) {
|
||||
_on_send(new_data, ".qoi");
|
||||
}
|
||||
}
|
||||
// error
|
||||
|
||||
|
@ -116,7 +116,6 @@ void SettingsWindow::render(void) {
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
void TextureEntry::doAnimation(const int64_t ts_now) {
|
||||
int64_t TextureEntry::doAnimation(const int64_t ts_now) {
|
||||
if (frame_duration.size() > 1) { // is animation
|
||||
do { // why is this loop so ugly
|
||||
const int64_t duration = getDuration();
|
||||
@ -11,11 +12,13 @@ void TextureEntry::doAnimation(const int64_t ts_now) {
|
||||
timestamp_last_rendered += duration;
|
||||
next();
|
||||
} else {
|
||||
break;
|
||||
// return ts for next frame
|
||||
return timestamp_last_rendered + duration;
|
||||
}
|
||||
} while(true);
|
||||
} while (true);
|
||||
} else {
|
||||
timestamp_last_rendered = ts_now;
|
||||
return std::numeric_limits<int64_t>::max(); // static image
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,8 @@ struct TextureEntry {
|
||||
current_texture = (current_texture + 1) % frame_duration.size();
|
||||
}
|
||||
|
||||
void doAnimation(const int64_t ts_now);
|
||||
// returns ts for next frame
|
||||
int64_t doAnimation(const int64_t ts_now);
|
||||
|
||||
template<typename TextureType>
|
||||
TextureType getID(void) {
|
||||
@ -133,14 +134,16 @@ struct TextureCache {
|
||||
}
|
||||
}
|
||||
|
||||
void update(void) {
|
||||
float update(void) {
|
||||
const uint64_t ts_now = Message::getTimeMS();
|
||||
uint64_t ts_min_next = ts_now + ms_before_purge;
|
||||
|
||||
std::vector<KeyType> to_purge;
|
||||
for (auto&& [key, te] : _cache) {
|
||||
if (te.rendered_this_frame) {
|
||||
te.doAnimation(ts_now);
|
||||
const uint64_t ts_next = te.doAnimation(ts_now);
|
||||
te.rendered_this_frame = false;
|
||||
ts_min_next = std::min(ts_min_next, ts_next);
|
||||
} else if (_cache.size() > min_count_before_purge && ts_now - te.timestamp_last_rendered >= ms_before_purge) {
|
||||
to_purge.push_back(key);
|
||||
}
|
||||
@ -148,7 +151,10 @@ struct TextureCache {
|
||||
|
||||
invalidate(to_purge);
|
||||
|
||||
// we ignore the default texture ts :)
|
||||
_default_texture.doAnimation(ts_now);
|
||||
|
||||
return (ts_min_next - ts_now) / 1000.f;
|
||||
}
|
||||
|
||||
void invalidate(const std::vector<KeyType>& to_purge) {
|
||||
@ -162,16 +168,22 @@ struct TextureCache {
|
||||
}
|
||||
}
|
||||
|
||||
void workLoadQueue(void) {
|
||||
for (auto it = _to_load.begin(); it != _to_load.end(); it++) {
|
||||
// returns true if there is still work queued up
|
||||
bool workLoadQueue(void) {
|
||||
auto it = _to_load.begin();
|
||||
for (; it != _to_load.end(); it++) {
|
||||
auto new_entry_opt = _l.load(_tu, *it);
|
||||
if (new_entry_opt.has_value()) {
|
||||
_cache.emplace(*it, new_entry_opt.value());
|
||||
_to_load.erase(it);
|
||||
// TODO: not a good idea
|
||||
it = _to_load.erase(it);
|
||||
|
||||
// TODO: not a good idea?
|
||||
break; // end load from queue/onlyload 1 per update
|
||||
}
|
||||
}
|
||||
|
||||
// peak
|
||||
return it != _to_load.end();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "./tox_avatar_loader.hpp"
|
||||
|
||||
#include "./image_loader_sdl_bmp.hpp"
|
||||
#include "./image_loader_qoi.hpp"
|
||||
#include "./image_loader_stb.hpp"
|
||||
#include "./image_loader_webp.hpp"
|
||||
|
||||
@ -21,6 +22,7 @@ uint64_t getTimeMS(void);
|
||||
|
||||
ToxAvatarLoader::ToxAvatarLoader(Contact3Registry& cr) : _cr(cr) {
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderQOI>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ std::string ToxAvatarManager::getAvatarPath(const ToxKey& key) const {
|
||||
const std::string_view avatar_save_path {_conf.get_string("ToxAvatarManager", "save_path").value()};
|
||||
const auto pub_key_string = bin2hex({key.data.cbegin(), key.data.cend()});
|
||||
const auto file_path = std::filesystem::path(avatar_save_path) / (pub_key_string + ".png");
|
||||
return file_path.u8string();
|
||||
return file_path.generic_u8string();
|
||||
}
|
||||
|
||||
void ToxAvatarManager::addAvatarFileToContact(const Contact3 c, const ToxKey& key) {
|
||||
|
213
src/tox_friend_faux_offline_messaging.cpp
Normal file
213
src/tox_friend_faux_offline_messaging.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
#include "./tox_friend_faux_offline_messaging.hpp"
|
||||
|
||||
#include <solanaceae/toxcore/tox_interface.hpp>
|
||||
|
||||
#include <solanaceae/contact/components.hpp>
|
||||
#include <solanaceae/tox_contacts/components.hpp>
|
||||
#include <solanaceae/message3/components.hpp>
|
||||
#include <solanaceae/tox_messages/components.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <cstdint>
|
||||
|
||||
//#include <iostream>
|
||||
|
||||
namespace Message::Components {
|
||||
struct LastSendAttempt {
|
||||
uint64_t ts {0};
|
||||
};
|
||||
} // Message::Components
|
||||
|
||||
namespace Contact::Components {
|
||||
struct NextSendAttempt {
|
||||
uint64_t ts {0};
|
||||
};
|
||||
} // Contact::Components
|
||||
|
||||
ToxFriendFauxOfflineMessaging::ToxFriendFauxOfflineMessaging(
|
||||
Contact3Registry& cr,
|
||||
RegistryMessageModel& rmm,
|
||||
ToxContactModel2& tcm,
|
||||
ToxI& t,
|
||||
ToxEventProviderI& tep
|
||||
) : _cr(cr), _rmm(rmm), _tcm(tcm), _t(t), _tep(tep) {
|
||||
_tep.subscribe(this, Tox_Event_Type::TOX_EVENT_FRIEND_CONNECTION_STATUS);
|
||||
}
|
||||
|
||||
float ToxFriendFauxOfflineMessaging::tick(float time_delta) {
|
||||
_interval_timer -= time_delta;
|
||||
if (_interval_timer > 0.f) {
|
||||
return std::max(_interval_timer, 0.001f); // TODO: min next timer
|
||||
}
|
||||
// interval ~ once per minute
|
||||
_interval_timer = 60.f;
|
||||
|
||||
|
||||
const uint64_t ts_now = Message::getTimeMS();
|
||||
|
||||
// check ALL
|
||||
// for each online tox friend
|
||||
uint64_t min_next_attempt_ts {std::numeric_limits<uint64_t>::max()};
|
||||
_cr.view<Contact::Components::ToxFriendEphemeral, Contact::Components::ConnectionState>()
|
||||
.each([this, &min_next_attempt_ts, ts_now](const Contact3 c, const auto& tfe, const auto& cs) {
|
||||
if (cs.state == Contact::Components::ConnectionState::disconnected) {
|
||||
// cleanup
|
||||
if (_cr.all_of<Contact::Components::NextSendAttempt>(c)) {
|
||||
_cr.remove<Contact::Components::NextSendAttempt>(c);
|
||||
auto* mr = static_cast<const RegistryMessageModel&>(_rmm).get(c);
|
||||
if (mr != nullptr) {
|
||||
mr->storage<Message::Components::LastSendAttempt>().clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!_cr.all_of<Contact::Components::NextSendAttempt>(c)) {
|
||||
if (false) { // has unsent messages
|
||||
const auto& nsa = _cr.emplace<Contact::Components::NextSendAttempt>(c, ts_now + uint64_t(_delay_after_cc*1000)); // wait before first message is sent
|
||||
min_next_attempt_ts = std::min(min_next_attempt_ts, nsa.ts);
|
||||
}
|
||||
} else {
|
||||
auto ret = doFriendMessageCheck(c, tfe);
|
||||
if (ret == dfmc_Ret::SENT_THIS_TICK) {
|
||||
const auto ts = _cr.get<Contact::Components::NextSendAttempt>(c).ts = ts_now + uint64_t(_delay_inbetween*1000);
|
||||
min_next_attempt_ts = std::min(min_next_attempt_ts, ts);
|
||||
} else if (ret == dfmc_Ret::TOO_SOON) {
|
||||
// TODO: set to _delay_inbetween? prob expensive for no good reason
|
||||
min_next_attempt_ts = std::min(min_next_attempt_ts, _cr.get<Contact::Components::NextSendAttempt>(c).ts);
|
||||
} else {
|
||||
_cr.remove<Contact::Components::NextSendAttempt>(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (min_next_attempt_ts <= ts_now) {
|
||||
// we (probably) sent this iterate
|
||||
_interval_timer = 0.1f; // TODO: ugly magic
|
||||
} else if (min_next_attempt_ts == std::numeric_limits<uint64_t>::max()) {
|
||||
// nothing to sync or all offline that need syncing
|
||||
} else {
|
||||
_interval_timer = std::min(_interval_timer, (min_next_attempt_ts - ts_now) / 1000.f);
|
||||
}
|
||||
|
||||
//std::cout << "TFFOM: iterate (i:" << _interval_timer << ")\n";
|
||||
|
||||
return _interval_timer;
|
||||
}
|
||||
|
||||
ToxFriendFauxOfflineMessaging::dfmc_Ret ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe) {
|
||||
// walk all messages and check if
|
||||
// unacked message
|
||||
// timeouts for exising unacked messages expired (send)
|
||||
|
||||
auto* mr = static_cast<const RegistryMessageModel&>(_rmm).get(c);
|
||||
if (mr == nullptr) {
|
||||
// no messages
|
||||
return dfmc_Ret::NO_MSG;
|
||||
}
|
||||
|
||||
const uint64_t ts_now = Message::getTimeMS();
|
||||
|
||||
// filter for unconfirmed messages
|
||||
|
||||
// we assume sorted
|
||||
// ("reverse" iteration <.<)
|
||||
auto msg_view = mr->view<Message::Components::Timestamp>();
|
||||
bool valid_unsent {false};
|
||||
// we search for the oldest, not too recently sent, unconfirmed message
|
||||
for (auto it = msg_view.rbegin(), view_end = msg_view.rend(); it != view_end; it++) {
|
||||
const Message3 msg = *it;
|
||||
|
||||
// require
|
||||
if (!mr->all_of<
|
||||
Message::Components::MessageText, // text only for now
|
||||
Message::Components::ContactTo
|
||||
>(msg)
|
||||
) {
|
||||
continue; // skip
|
||||
}
|
||||
|
||||
// exclude
|
||||
if (mr->any_of<
|
||||
Message::Components::Remote::TimestampReceived // this acts like a tag, which is wrong in groups
|
||||
>(msg)
|
||||
) {
|
||||
continue; // skip
|
||||
}
|
||||
|
||||
if (mr->get<Message::Components::ContactTo>(msg).c != c) {
|
||||
continue; // not outbound (in private)
|
||||
}
|
||||
|
||||
valid_unsent = true;
|
||||
|
||||
uint64_t msg_ts = msg_view.get<Message::Components::Timestamp>(msg).ts;
|
||||
if (mr->all_of<Message::Components::TimestampWritten>(msg)) {
|
||||
msg_ts = mr->get<Message::Components::TimestampWritten>(msg).ts;
|
||||
}
|
||||
if (mr->all_of<Message::Components::LastSendAttempt>(msg)) {
|
||||
const auto lsa = mr->get<Message::Components::LastSendAttempt>(msg).ts;
|
||||
if (lsa > msg_ts) {
|
||||
msg_ts = lsa;
|
||||
}
|
||||
}
|
||||
|
||||
if (ts_now < (msg_ts + uint64_t(_delay_retry * 1000))) {
|
||||
// not time yet
|
||||
continue;
|
||||
}
|
||||
|
||||
// it is time
|
||||
const auto [msg_id, _] = _t.toxFriendSendMessage(
|
||||
tfe.friend_number,
|
||||
(
|
||||
mr->all_of<Message::Components::TagMessageIsAction>(msg)
|
||||
? Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION
|
||||
: Tox_Message_Type::TOX_MESSAGE_TYPE_NORMAL
|
||||
),
|
||||
mr->get<Message::Components::MessageText>(msg).text
|
||||
);
|
||||
|
||||
// TODO: this is ugly
|
||||
mr->emplace_or_replace<Message::Components::LastSendAttempt>(msg, ts_now);
|
||||
|
||||
if (msg_id.has_value()) {
|
||||
// tmm will pick this up for us
|
||||
mr->emplace_or_replace<Message::Components::ToxFriendMessageID>(msg, msg_id.value());
|
||||
} // else error
|
||||
|
||||
// we sent our message, no point further iterating
|
||||
return dfmc_Ret::SENT_THIS_TICK;
|
||||
}
|
||||
|
||||
if (!valid_unsent) {
|
||||
// somehow cleanup lsa
|
||||
mr->storage<Message::Components::LastSendAttempt>().clear();
|
||||
//std::cout << "TFFOM: all sent, deleting lsa\n";
|
||||
return dfmc_Ret::NO_MSG;
|
||||
}
|
||||
|
||||
return dfmc_Ret::TOO_SOON;
|
||||
}
|
||||
|
||||
bool ToxFriendFauxOfflineMessaging::onToxEvent(const Tox_Event_Friend_Connection_Status* e) {
|
||||
const auto friend_number = tox_event_friend_connection_status_get_friend_number(e);
|
||||
const auto friend_status = tox_event_friend_connection_status_get_connection_status(e);
|
||||
|
||||
if (friend_status == Tox_Connection::TOX_CONNECTION_NONE) {
|
||||
return false; // skip
|
||||
// maybe cleanup?
|
||||
}
|
||||
|
||||
auto c = _tcm.getContactFriend(friend_number);
|
||||
if (!static_cast<bool>(c) || !c.all_of<Contact::Components::ToxFriendEphemeral, Contact::Components::ConnectionState>()) {
|
||||
// UH error??
|
||||
return false;
|
||||
}
|
||||
|
||||
_cr.emplace_or_replace<Contact::Components::NextSendAttempt>(c, Message::getTimeMS() + uint64_t(_delay_after_cc*1000)); // wait before first message is sent
|
||||
|
||||
_interval_timer = 0.f;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
55
src/tox_friend_faux_offline_messaging.hpp
Normal file
55
src/tox_friend_faux_offline_messaging.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/toxcore/tox_event_interface.hpp>
|
||||
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
#include <solanaceae/message3/registry_message_model.hpp>
|
||||
|
||||
// fwd
|
||||
struct ToxI;
|
||||
namespace Contact::Components {
|
||||
struct ToxFriendEphemeral;
|
||||
}
|
||||
|
||||
// resends unconfirmed messages.
|
||||
// timers get reset on connection changes, and send order is preserved.
|
||||
class ToxFriendFauxOfflineMessaging : public ToxEventI {
|
||||
Contact3Registry& _cr;
|
||||
RegistryMessageModel& _rmm;
|
||||
ToxContactModel2& _tcm;
|
||||
ToxI& _t;
|
||||
ToxEventProviderI& _tep;
|
||||
|
||||
float _interval_timer{0.f};
|
||||
|
||||
// TODO: increase timer?
|
||||
const float _delay_after_cc {4.5f};
|
||||
const float _delay_inbetween {0.3f};
|
||||
const float _delay_retry {10.f}; // retry sending after 10s
|
||||
|
||||
public:
|
||||
ToxFriendFauxOfflineMessaging(
|
||||
Contact3Registry& cr,
|
||||
RegistryMessageModel& rmm,
|
||||
ToxContactModel2& tcm,
|
||||
ToxI& t,
|
||||
ToxEventProviderI& tep
|
||||
);
|
||||
|
||||
float tick(float time_delta);
|
||||
|
||||
private:
|
||||
enum class dfmc_Ret {
|
||||
TOO_SOON,
|
||||
SENT_THIS_TICK,
|
||||
NO_MSG,
|
||||
};
|
||||
// only called for online friends
|
||||
// returns true if a message was sent
|
||||
// dont call this too often
|
||||
dfmc_Ret doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe);
|
||||
|
||||
protected:
|
||||
bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override;
|
||||
};
|
||||
|
Reference in New Issue
Block a user