aboutsummaryrefslogtreecommitdiff
path: root/bv1
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-03-09 22:48:33 +0100
committermetamuffin <metamuffin@disroot.org>2023-03-09 22:48:33 +0100
commit680b08e6b9d64284b7992fb52a23e5f891291406 (patch)
treeabe30a9f18be09ef931b4b6357d216f6ba982095 /bv1
parentc45f80a14ecd00914eb1d4e8f628b74a713667ba (diff)
downloadvideo-codec-experiments-680b08e6b9d64284b7992fb52a23e5f891291406.tar
video-codec-experiments-680b08e6b9d64284b7992fb52a23e5f891291406.tar.bz2
video-codec-experiments-680b08e6b9d64284b7992fb52a23e5f891291406.tar.zst
rename + readme
Diffstat (limited to 'bv1')
-rw-r--r--bv1/.gitignore2
-rw-r--r--bv1/Cargo.lock552
-rw-r--r--bv1/Cargo.toml3
-rw-r--r--bv1/app/Cargo.toml9
-rw-r--r--bv1/app/src/bin/bench.rs28
-rw-r--r--bv1/app/src/bin/main.rs68
-rw-r--r--bv1/app/src/bin/test.rs47
-rw-r--r--bv1/codec-web/Cargo.toml13
-rw-r--r--bv1/codec-web/src/lib.rs60
-rw-r--r--bv1/codec-web/web/.gitignore2
-rw-r--r--bv1/codec-web/web/index.html14
-rw-r--r--bv1/codec-web/web/main.ts69
-rw-r--r--bv1/codec/Cargo.toml12
-rw-r--r--bv1/codec/src/debug.rs42
-rw-r--r--bv1/codec/src/decode.rs76
-rw-r--r--bv1/codec/src/diff.rs131
-rw-r--r--bv1/codec/src/encode.rs190
-rw-r--r--bv1/codec/src/frameio.rs35
-rw-r--r--bv1/codec/src/huff.rs190
-rw-r--r--bv1/codec/src/impls.rs165
-rw-r--r--bv1/codec/src/lib.rs55
-rw-r--r--bv1/codec/src/serialize.rs116
-rw-r--r--bv1/codec/src/split.rs42
-rwxr-xr-xbv1/tools/d-disp2
-rwxr-xr-xbv1/tools/d-dispd2
-rwxr-xr-xbv1/tools/d-save2
-rwxr-xr-xbv1/tools/d-saved2
-rwxr-xr-xbv1/tools/e2
28 files changed, 1931 insertions, 0 deletions
diff --git a/bv1/.gitignore b/bv1/.gitignore
new file mode 100644
index 0000000..2f22d72
--- /dev/null
+++ b/bv1/.gitignore
@@ -0,0 +1,2 @@
+/data
+/target
diff --git a/bv1/Cargo.lock b/bv1/Cargo.lock
new file mode 100644
index 0000000..cd51668
--- /dev/null
+++ b/bv1/Cargo.lock
@@ -0,0 +1,552 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "app"
+version = "0.1.0"
+dependencies = [
+ "bv1",
+ "clap",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "bv1"
+version = "0.1.0"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
+dependencies = [
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "is-terminal",
+ "once_cell",
+ "strsim",
+ "termcolor",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "codec-web"
+version = "0.1.0"
+dependencies = [
+ "bv1",
+ "console_error_panic_hook",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
+[[package]]
+name = "rustix"
+version = "0.36.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/bv1/Cargo.toml b/bv1/Cargo.toml
new file mode 100644
index 0000000..489eec7
--- /dev/null
+++ b/bv1/Cargo.toml
@@ -0,0 +1,3 @@
+[workspace]
+members = ["codec", "app", "codec-web"]
+default-members = ["app", "codec-web"]
diff --git a/bv1/app/Cargo.toml b/bv1/app/Cargo.toml
new file mode 100644
index 0000000..e433e4f
--- /dev/null
+++ b/bv1/app/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "app"
+version = "0.1.0"
+edition = "2021"
+default-run = "main"
+
+[dependencies]
+clap = { version = "4.1.8", features = ["derive"] }
+bv1 = { path = "../codec" }
diff --git a/bv1/app/src/bin/bench.rs b/bv1/app/src/bin/bench.rs
new file mode 100644
index 0000000..b9c6464
--- /dev/null
+++ b/bv1/app/src/bin/bench.rs
@@ -0,0 +1,28 @@
+use bv1::{diff::*, Frame, Ref, View, P2};
+use std::time::Instant;
+
+fn measure(f: impl FnOnce()) {
+ let t1 = Instant::now();
+ f();
+ let t2 = Instant::now();
+ eprintln!("took {:?}", (t2 - t1));
+}
+
+fn main() {
+ let size = P2 { x: 2000, y: 2000 };
+ let f1 = Frame::new(size);
+ let f2 = Frame::new(size);
+ measure(|| {
+ diff([&f1, &f2], View::all(size), Ref::default());
+ });
+}
+
+// #[test]
+// fn bench_fast_diff() {
+// let size = P2 { x: 2000, y: 2000 };
+// let f1 = Frame::new(size);
+// let f2 = Frame::new(size);
+// measure(|| {
+// diff_fast([&f1, &f2], View::all(size), Ref::default());
+// });
+// }
diff --git a/bv1/app/src/bin/main.rs b/bv1/app/src/bin/main.rs
new file mode 100644
index 0000000..0b3596e
--- /dev/null
+++ b/bv1/app/src/bin/main.rs
@@ -0,0 +1,68 @@
+use bv1::{
+ encode::{encode, EncodeConfig},
+ P2, decode,
+};
+use clap::{Parser, Subcommand};
+use std::io::{stdin, stdout};
+
+#[derive(Parser)]
+#[clap(about, version)]
+struct Args {
+ // Width of the video signal
+ #[arg(short = 'W', long)]
+ width: u16,
+ // Height of the video signal
+ #[arg(short = 'H', long)]
+ height: u16,
+ #[clap(subcommand)]
+ action: Action,
+}
+
+#[derive(Clone, Subcommand)]
+enum Action {
+ // Compress video
+ Encode {
+ #[arg(short, long, default_value_t = 800)]
+ max_block_size: usize,
+ #[arg(short, long, default_value_t = 10_000)]
+ attention_split: u32,
+ #[arg(short, long, default_value_t = 10.)]
+ threshold: f32,
+ #[arg(short, long, default_value_t = 10)]
+ keyframe_interval: usize,
+ },
+ // Decompress video
+ Decode {
+ #[arg(short, long)]
+ debug: bool,
+ },
+}
+
+fn main() {
+ let args = Args::parse();
+
+ let size = P2 {
+ x: args.width as i32,
+ y: args.height as i32,
+ };
+ match args.action {
+ Action::Encode {
+ max_block_size,
+ threshold,
+ attention_split,
+ keyframe_interval,
+ } => {
+ let config = EncodeConfig {
+ threshold,
+ max_block_size,
+ attention_split,
+ keyframe_interval,
+ };
+
+ encode(config, size, stdin(), stdout()).unwrap();
+ }
+ Action::Decode { debug } => {
+ decode(size, debug, stdin(), stdout()).unwrap();
+ }
+ }
+}
diff --git a/bv1/app/src/bin/test.rs b/bv1/app/src/bin/test.rs
new file mode 100644
index 0000000..4071ff1
--- /dev/null
+++ b/bv1/app/src/bin/test.rs
@@ -0,0 +1,47 @@
+#[cfg(test)]
+mod test {
+
+ use bv1::huff::{read_huff, write_huff, BitIO};
+ use std::io::Cursor;
+
+ #[test]
+ fn test_bitio() {
+ let mut buf = Vec::<u8>::new();
+
+ {
+ let mut b = BitIO::new(Cursor::new(&mut buf));
+ b.wbit(true).unwrap();
+ b.wbit(true).unwrap();
+ b.wbit(true).unwrap();
+ b.wbit(true).unwrap();
+ b.wbit(false).unwrap();
+ b.wbit(true).unwrap();
+ b.wbit(true).unwrap();
+ b.wbit(true).unwrap();
+ b.wbit(true).unwrap();
+ b.wbyte(0xff).unwrap();
+ b.flush().unwrap();
+ }
+ {
+ let mut b = BitIO::new(Cursor::new(&mut buf));
+ for _ in 0..17 {
+ let _v = b.rbit().unwrap();
+ // eprintln!("{:?}", _v)
+ }
+ }
+ }
+
+ #[test]
+ fn test_huff() {
+ let a = vec![1, 2, 3, 4, 5, 1, 3, 6, 3, 2, 4, 6, 7, 4, 3, 2, 1, 3, 4];
+ let mut b = vec![];
+
+ let mut buf = Vec::<u8>::new();
+ write_huff(&a, &mut Cursor::new(&mut buf)).unwrap();
+ read_huff(&mut Cursor::new(&mut buf), &mut b).unwrap();
+
+ assert_eq!(a, b)
+ }
+}
+
+fn main() {}
diff --git a/bv1/codec-web/Cargo.toml b/bv1/codec-web/Cargo.toml
new file mode 100644
index 0000000..f1a3f6a
--- /dev/null
+++ b/bv1/codec-web/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "codec-web"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+bv1 = { path = "../codec" }
+wasm-bindgen = "0.2.84"
+js-sys = "0.3.61"
+console_error_panic_hook = "0.1.7"
diff --git a/bv1/codec-web/src/lib.rs b/bv1/codec-web/src/lib.rs
new file mode 100644
index 0000000..cb2ad3c
--- /dev/null
+++ b/bv1/codec-web/src/lib.rs
@@ -0,0 +1,60 @@
+use bv1::{Decoder, Frame, P2};
+use std::{collections::VecDeque, sync::RwLock};
+use wasm_bindgen::prelude::*;
+
+static DECODER: RwLock<Option<State>> = RwLock::new(None);
+
+struct State {
+ buffer: VecDeque<u8>,
+ frame: Frame,
+ decoder: Decoder,
+}
+
+// #[wasm_bindgen]
+// extern "C" {
+// #[wasm_bindgen(js_namespace = console)]
+// fn log(s: &str);
+// #[wasm_bindgen(js_namespace = console, js_name = "log")]
+// fn logs(s: String);
+// }
+
+#[wasm_bindgen(start)]
+fn panic_init() {
+ std::panic::set_hook(Box::new(console_error_panic_hook::hook));
+}
+
+#[wasm_bindgen]
+pub fn decode_init(width: i32, height: i32) {
+ let size = P2 {
+ x: width,
+ y: height,
+ };
+ *DECODER.write().unwrap() = Some(State {
+ frame: Frame::new(size),
+ decoder: Decoder::new(size),
+ buffer: VecDeque::new(),
+ });
+}
+
+#[wasm_bindgen]
+pub fn decode_frame(buf: &[u8], debug: bool) -> Vec<u8> {
+ let mut arr = Vec::new();
+ let mut g = DECODER.write().unwrap();
+ let state = g.as_mut().unwrap();
+ state.buffer.extend(buf.iter());
+
+ state
+ .decoder
+ .decode_frame(&mut state.buffer, &mut state.frame, debug)
+ .unwrap();
+
+ for y in 0..state.frame.size.y {
+ for x in 0..state.frame.size.x {
+ arr.push(state.frame[P2 { x, y }].r.clamp(0, 255) as u8);
+ arr.push(state.frame[P2 { x, y }].g.clamp(0, 255) as u8);
+ arr.push(state.frame[P2 { x, y }].b.clamp(0, 255) as u8);
+ }
+ }
+
+ arr
+}
diff --git a/bv1/codec-web/web/.gitignore b/bv1/codec-web/web/.gitignore
new file mode 100644
index 0000000..93d7118
--- /dev/null
+++ b/bv1/codec-web/web/.gitignore
@@ -0,0 +1,2 @@
+/codec_web*
+/bundle*
diff --git a/bv1/codec-web/web/index.html b/bv1/codec-web/web/index.html
new file mode 100644
index 0000000..b572671
--- /dev/null
+++ b/bv1/codec-web/web/index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>bv1 web player</title>
+ <script defer async src="./bundle.js" type="module"></script>
+ </head>
+ <body>
+ <noscript>need js</noscript>
+ <p>loading…</p>
+ </body>
+</html>
diff --git a/bv1/codec-web/web/main.ts b/bv1/codec-web/web/main.ts
new file mode 100644
index 0000000..b1b1df7
--- /dev/null
+++ b/bv1/codec-web/web/main.ts
@@ -0,0 +1,69 @@
+/// <reference lib="dom" />
+
+import init, { decode_frame, decode_init } from "./codec_web.js"
+console.log("init wasm");
+await init()
+console.log("done");
+
+index("..")
+
+async function index(url: string) {
+ const res = await fetch(url)
+ if (!res.ok) throw new Error("not ok");
+ document.body.innerHTML = await res.text();
+ const h1 = document.createElement("h1")
+ h1.textContent = "bv1 web player"
+ document.body.prepend(h1)
+ document.body.querySelectorAll("a").forEach(e => {
+ const u = url + "/" + e.textContent
+ e.onclick = ev => {
+ ev.preventDefault()
+ if (!u.endsWith(".bv1")) return alert("thats not bv1!")
+ document.body.innerHTML = ""
+ play(u.toString())
+ }
+ })
+}
+
+async function play(url: string) {
+ decode_init(1920, 1080)
+ document.body.innerText = "downloading…"
+ const res = await fetch(url)
+ if (!res.ok) throw new Error("not ok");
+ let buf: Uint8Array | undefined = new Uint8Array(await res.arrayBuffer())
+ document.body.innerText = ""
+
+ const canvas = document.createElement("canvas")
+ canvas.width = 1920
+ canvas.height = 1080
+ document.body.append(canvas)
+ document.body.style.backgroundColor = "#111"
+ const ctx = canvas.getContext("2d")!
+ let debug = false;
+
+ document.body.addEventListener("keydown", ev => {
+ if (ev.code == "KeyD") debug = !debug;
+ })
+
+ setInterval(() => {
+ const frame = decode_frame(buf ?? new Uint8Array(), debug);
+ buf = undefined
+
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ const data = imageData.data;
+
+ for (let y = 0; y < 1080; y++) {
+ for (let x = 0; x < 1920; x++) {
+ const ti = (x + y * 1920) * 4;
+ const si = (x + y * 1920) * 3;
+ data[ti + 0] = frame[si + 0]
+ data[ti + 1] = frame[si + 1]
+ data[ti + 2] = frame[si + 2]
+ data[ti + 3] = 255
+ }
+ }
+
+ ctx.putImageData(imageData, 0, 0);
+
+ }, 1000 / 30)
+}
diff --git a/bv1/codec/Cargo.toml b/bv1/codec/Cargo.toml
new file mode 100644
index 0000000..0c52ad2
--- /dev/null
+++ b/bv1/codec/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "bv1"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+rayon = {version="1.7.0",optional = true}
+
+[features]
+default = ["parallel"]
+parallel = ["dep:rayon"]
+
diff --git a/bv1/codec/src/debug.rs b/bv1/codec/src/debug.rs
new file mode 100644
index 0000000..d6c37c2
--- /dev/null
+++ b/bv1/codec/src/debug.rs
@@ -0,0 +1,42 @@
+use crate::{split::split, Block, Frame, Pixel, View, P2};
+
+pub fn draw_debug(frame: &mut Frame, view: View, block: &Block) {
+ match block {
+ Block::Lit(_) => rect(frame, view, Pixel::GREEN),
+ Block::Split(a, b) => {
+ let [av, bv] = split(view);
+ draw_debug(frame, av, &a);
+ draw_debug(frame, bv, &b);
+ }
+ Block::Ref(r) => {
+ let v = View {
+ a: view.a,
+ b: view.a + P2 { x: 2, y: 2 },
+ };
+ if r.pos_off != P2::ZERO {
+ fill_rect(frame, v + P2 { x: 0, y: 0 }, Pixel::BLUE)
+ }
+ if r.color_off != Pixel::BLACK {
+ fill_rect(frame, v + P2 { x: 2, y: 0 }, Pixel::RED)
+ }
+ }
+ }
+}
+
+fn rect(frame: &mut Frame, view: View, color: Pixel) {
+ for x in view.a.x..view.b.x {
+ frame[P2 { x, y: view.a.y }] = color;
+ frame[P2 { x, y: view.b.y - 1 }] = color;
+ }
+ for y in view.a.y..view.b.y {
+ frame[P2 { y, x: view.a.x }] = color;
+ frame[P2 { y, x: view.b.x - 1 }] = color;
+ }
+}
+fn fill_rect(frame: &mut Frame, view: View, color: Pixel) {
+ for y in view.a.y..view.b.y {
+ for x in view.a.x..view.b.x {
+ frame[P2 { x, y }] = color;
+ }
+ }
+}
diff --git a/bv1/codec/src/decode.rs b/bv1/codec/src/decode.rs
new file mode 100644
index 0000000..616b7af
--- /dev/null
+++ b/bv1/codec/src/decode.rs
@@ -0,0 +1,76 @@
+use crate::{
+ debug::draw_debug, huff::read_huff, impls::join, split::split, Block, Frame, View, P2,
+};
+use std::io::{BufReader, BufWriter, Read, Result, Write};
+
+pub fn decode(size: P2, debug: bool, input: impl Read, output: impl Write) -> Result<()> {
+ let mut input = BufReader::new(input);
+ let mut output = BufWriter::new(output);
+ let mut d = Decoder::new(size);
+ let mut f = Frame::new(size);
+ loop {
+ d.decode_frame(&mut input, &mut f, debug)?;
+ Frame::write(&mut output, &f)?;
+ }
+}
+
+pub struct Decoder {
+ last_frame: Frame,
+ size: P2,
+}
+
+impl Decoder {
+ pub fn new(size: P2) -> Self {
+ Self {
+ size,
+ last_frame: Frame::new(size),
+ }
+ }
+ pub fn decode_frame(
+ &mut self,
+ mut input: impl Read,
+ output: &mut Frame,
+ debug: bool,
+ ) -> Result<()> {
+ let huff = true;
+ let b = if huff {
+ let mut buf = vec![];
+ read_huff(&mut input, &mut buf)?;
+ eprintln!("{}", buf.len());
+ let mut buf = std::io::Cursor::new(&mut buf);
+ Block::read(&mut buf, View::all(self.size))?
+ } else {
+ Block::read(&mut input, View::all(self.size))?
+ };
+
+ decode_block(&self.last_frame, output, View::all(self.size), &b);
+ self.last_frame.pixels.copy_from_slice(&output.pixels); // TODO use mem::swap
+ if debug {
+ draw_debug(output, View::all(self.size), &b);
+ }
+ Ok(())
+ }
+}
+
+pub fn decode_block(last_frame: &Frame, frame: &mut Frame, view: View, block: &Block) {
+ match block {
+ Block::Lit(pxs) => frame.import(view, &pxs),
+ Block::Split(a, b) => {
+ let [av, bv] = split(view);
+ let (frame1, frame2) =
+ unsafe { (&mut *(frame as *mut Frame), &mut *(frame as *mut Frame)) };
+ join(
+ || decode_block(last_frame, frame1, av, &a),
+ || decode_block(last_frame, frame2, bv, &b),
+ );
+ }
+ Block::Ref(r) => {
+ for y in view.a.y..view.b.y {
+ for x in view.a.x..view.b.x {
+ let p = P2 { x, y };
+ frame[p] = last_frame[p + r.pos_off] + r.color_off
+ }
+ }
+ }
+ }
+}
diff --git a/bv1/codec/src/diff.rs b/bv1/codec/src/diff.rs
new file mode 100644
index 0000000..4d1a805
--- /dev/null
+++ b/bv1/codec/src/diff.rs
@@ -0,0 +1,131 @@
+use crate::{Frame, Pixel, Ref, View, P2};
+
+// 4ms
+pub fn diff([frame1, frame2]: [&Frame; 2], view: View, rp: Ref) -> u32 {
+ let mut k = 0;
+ for y in view.a.y..view.b.y {
+ for x in view.a.x..view.b.x {
+ let pos = P2 { x, y };
+ let p1 = frame1[pos + rp.pos_off] + rp.color_off;
+ let p2 = frame2[pos];
+ k += pixel_diff(p1, p2)
+ }
+ }
+ k
+}
+
+#[inline(always)]
+pub fn pixel_diff(p1: Pixel, p2: Pixel) -> u32 {
+ p1.r.abs_diff(p2.r) as u32 + p1.g.abs_diff(p2.g) as u32 + p1.b.abs_diff(p2.b) as u32
+}
+
+// pub fn diff([frame1, frame2]: [&Frame; 2], view: View, rp: Ref) -> u32 {
+// let mut k = 0;
+// for y in view.a.y..view.b.y {
+// let s1_ystart = (y * frame1.size.x) as usize;
+// let s2_ystart = ((y + rp.pos_off.y) * frame1.size.x + rp.pos_off.x) as usize;
+// let s1 = &frame1.pixels[s1_ystart + view.a.x as usize..s1_ystart + view.b.x as usize];
+// let s2 = &frame2.pixels[s2_ystart + view.a.x as usize..s2_ystart + view.b.x as usize];
+// let s1 = unsafe { std::mem::transmute::<_, &[i16]>(s1) };
+// let s2 = unsafe { std::mem::transmute::<_, &[i16]>(s2) };
+
+// k += s1
+// .iter()
+// .zip(s2.iter())
+// .map(|(a, b)| a.abs_diff(*b) as u32)
+// .sum::<u32>()
+// }
+// k
+// }
+
+// pub fn fast_diff([frame1, frame2]: [&Frame; 2], view: View, rp: Ref) -> u32 {
+// assert!(view.size().x % 5 == 0);
+
+// let mut diff_lanes = i32x16::from_array([0; 16]);
+// let mut k1 = [0; 32];
+// let mut k2 = [0; 32];
+
+// let next_line = frame1.size.x as usize - view.size().x as usize;
+// let index_start = view.a.x as usize + view.a.y as usize * frame1.size.x as usize;
+// let index_end = view.b.x as usize + (view.b.y as usize - 1) * frame1.size.x as usize;
+
+// let mut i = index_start;
+// let mut x = view.a.x;
+
+// while i < index_end {
+
+// let f1: &[u8] =
+// unsafe { std::slice::from_raw_parts(frame1.pixels[sl_start..].as_ptr() as *mut u8, sl_size) };
+// let f2: &[u8] =
+// unsafe { std::slice::from_raw_parts(frame2.pixels[sl_start..].as_ptr() as *mut u8, sl_size) };
+
+// for i in 0..15 {
+// k1[i] = f1[i] as i32;
+// k2[i] = f2[i] as i32;
+// }
+
+// // for j in 0..5 {
+// // let j3 = j * 3;
+// // k1[j3] = frame1.pixels[i + j].r as i32;
+// // k2[j3] = frame2.pixels[i + j].r as i32;
+// // k1[j3 + 1] = frame1.pixels[i + j].g as i32;
+// // k2[j3 + 1] = frame2.pixels[i + j].g as i32;
+// // k1[j3 + 2] = frame1.pixels[i + j].b as i32;
+// // k2[j3 + 2] = frame2.pixels[i + j].b as i32;
+// // }
+// let pl1 = i16x32::from_array(k1);
+// let pl2 = i16x32::from_array(k2);
+// diff_lanes += (pl1 - pl2).abs();
+
+// i += 5;
+// x += 5;
+// if x > view.b.x {
+// i += next_line;
+// x = view.a.x
+// }
+// }
+
+// return diff_lanes.reduce_sum() as u32;
+// }
+
+// pub fn fast_diff([frame1, frame2]: [&Frame; 2], view: View, rp: Ref) -> u32 {
+// let mut diff_lanes = i32x16::from_array([0; 16]);
+// let mut k1 = [0; 16];
+// let mut k2 = [0; 16];
+
+// let next_line = frame1.size.x as usize - view.size().x as usize;
+// let index_start = view.a.x as usize + view.a.y as usize * frame1.size.x as usize;
+// let index_end = view.b.x as usize + (view.b.y as usize - 1) * frame1.size.x as usize;
+
+// let mut i = index_start;
+// let mut x = view.a.x;
+// let mut kfill = 0;
+
+// while i < index_end {
+// k1[kfill] = frame1.pixels[i].r as i32;
+// k2[kfill] = frame2.pixels[i].r as i32;
+// kfill += 1;
+// k1[kfill] = frame1.pixels[i].g as i32;
+// k2[kfill] = frame2.pixels[i].g as i32;
+// kfill += 1;
+// k1[kfill] = frame1.pixels[i].b as i32;
+// k2[kfill] = frame2.pixels[i].b as i32;
+// kfill += 1;
+
+// i += 1;
+// x += 1;
+// if x > view.b.x {
+// i += next_line;
+// x = view.a.x
+// }
+
+// if kfill == 15 {
+// let pl1 = i32x16::from_array(k1);
+// let pl2 = i32x16::from_array(k2);
+// diff_lanes += (pl1 - pl2).abs();
+// kfill = 0;
+// }
+// }
+
+// return diff_lanes.reduce_sum() as u32;
+// }
diff --git a/bv1/codec/src/encode.rs b/bv1/codec/src/encode.rs
new file mode 100644
index 0000000..564b0b3
--- /dev/null
+++ b/bv1/codec/src/encode.rs
@@ -0,0 +1,190 @@
+use crate::diff::{diff, pixel_diff};
+use crate::huff::write_huff;
+use crate::impls::join;
+use crate::split::split;
+use crate::{decode::decode_block, Block, Frame, Pixel, Ref, View, P2};
+use std::io::{BufReader, BufWriter, Read, Write};
+use std::time::Instant;
+
+#[derive(Debug, Clone)]
+pub struct EncodeConfig {
+ pub threshold: f32,
+ pub max_block_size: usize,
+ pub attention_split: u32,
+ pub keyframe_interval: usize,
+}
+
+pub fn encode(
+ config: EncodeConfig,
+ size: P2,
+ input: impl Read,
+ output: impl Write,
+) -> std::io::Result<()> {
+ let mut input = BufReader::new(input);
+ let mut output = BufWriter::new(output);
+
+ let mut last_frame = Frame::new(size);
+ for frame_number in 0.. {
+ let mut frame = Frame::read(&mut input, size)?;
+
+ let mut config = config.clone();
+ if frame_number % config.keyframe_interval != 0 {
+ config.threshold = std::f32::INFINITY;
+ }
+
+ let t = Instant::now();
+ let b: Block = encode_block(&last_frame, &frame, View::all(size), &config);
+ let time_encode = t.elapsed();
+
+ let t = Instant::now();
+ decode_block(&last_frame, &mut frame, View::all(size), &b);
+ last_frame = frame;
+ let time_decode = t.elapsed();
+
+ if true {
+ let mut buf = vec![];
+ let mut bufw = std::io::Cursor::new(&mut buf);
+ b.write(&mut bufw)?;
+ drop(bufw);
+ let t = Instant::now();
+ let bits_raw = buf.len() * 8;
+ let bits_huff = write_huff(&buf, &mut output)?;
+ let time_huff = t.elapsed();
+ drop(buf);
+
+ eprintln!(
+ "frame {frame_number}: {:?}",
+ time_decode + time_huff + time_encode
+ );
+ eprintln!(
+ "\tencode {time_encode:?} ({:.2}%)",
+ (bits_raw as f32 / (size.area() * 24) as f32) * 100.0
+ );
+ eprintln!(
+ "\thuff {time_huff:?} ({:.2}%)",
+ (bits_huff as f32 / bits_raw as f32) * 100.0
+ );
+ eprintln!("\tdecode {time_decode:?}");
+ } else {
+ b.write(&mut output)?;
+ }
+ }
+ Ok(())
+}
+
+pub fn encode_block(last_frame: &Frame, frame: &Frame, view: View, config: &EncodeConfig) -> Block {
+ let view_area = view.size().area();
+ if view_area > config.max_block_size
+ || (view_area > 64 && attention(frame, view) > config.attention_split)
+ {
+ let [av, bv] = split(view);
+ let (ab, bb) = join(
+ || Box::new(encode_block(last_frame, frame, av, config)),
+ || Box::new(encode_block(last_frame, frame, bv, config)),
+ );
+ return Block::Split(ab, bb);
+ }
+
+ let mut r = Ref::default();
+ let mut d = diff([last_frame, frame], view, r);
+
+ // let att = 1. - attention(frame, view) as f32 * 0.000001;
+ // let thres = (config.threshold as f32 * att.clamp(0.2, 1.0)) as u32;
+ let thres = (config.threshold * view_area as f32) as u32;
+
+ let target_average = average_color(frame, view);
+
+ for granularity in [2, 1, 2, 1, 2, 1, 2, 1] {
+ let (nd, nrp) = optimize_ref(last_frame, frame, view, r, granularity, target_average);
+ if nd < d {
+ r = nrp;
+ d = nd;
+ } else {
+ break;
+ }
+ }
+
+ if d < thres {
+ return Block::Ref(r);
+ } else {
+ Block::Lit(frame.export(view))
+ }
+}
+
+pub fn optimize_ref(
+ last_frame: &Frame,
+ frame: &Frame,
+ view: View,
+ r: Ref,
+ g: i32,
+ target_average: Pixel,
+) -> (u32, Ref) {
+ let g2 = g * 2;
+ [
+ Some(r.apply(|r| r.pos_off += P2 { x: g, y: 0 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: g, y: g })),
+ Some(r.apply(|r| r.pos_off += P2 { x: 0, y: g })),
+ Some(r.apply(|r| r.pos_off += P2 { x: -g, y: g })),
+ Some(r.apply(|r| r.pos_off += P2 { x: -g, y: 0 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: -g, y: -g })),
+ Some(r.apply(|r| r.pos_off += P2 { x: 0, y: -g })),
+ Some(r.apply(|r| r.pos_off += P2 { x: g, y: -g })),
+ Some(r.apply(|r| r.pos_off += P2 { x: g2, y: 0 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: g2, y: g2 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: 0, y: g2 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: -g2, y: g2 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: -g2, y: 0 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: -g2, y: -g2 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: 0, y: -g2 })),
+ Some(r.apply(|r| r.pos_off += P2 { x: g2, y: -g2 })),
+ {
+ let mut r = r;
+ let last_avr = average_color(last_frame, view);
+ let diff = target_average - last_avr;
+ r.color_off = diff;
+ if diff != Pixel::BLACK {
+ Some(r)
+ } else {
+ None
+ }
+ },
+ ]
+ .into_iter()
+ .flatten()
+ .map(|r| (diff([last_frame, frame], view, r), r))
+ .min_by_key(|e| e.0)
+ .unwrap()
+}
+
+pub fn attention(frame: &Frame, view: View) -> u32 {
+ let mut k = 0;
+ for y in view.a.y..view.b.y - 1 {
+ for x in view.a.x..view.b.x - 1 {
+ let p = P2 { x, y };
+ k += pixel_diff(frame[p], frame[p + P2::X]).pow(2);
+ k += pixel_diff(frame[p], frame[p + P2::Y]).pow(2);
+ }
+ }
+ k
+}
+
+pub fn average_color(frame: &Frame, view: View) -> Pixel {
+ let mut r = 0u32;
+ let mut g = 0u32;
+ let mut b = 0u32;
+
+ for y in view.a.y..view.b.y {
+ for x in view.a.x..view.b.x {
+ let p = frame[P2 { x, y }];
+ r += p.r as u32;
+ g += p.g as u32;
+ b += p.b as u32;
+ }
+ }
+ let area = view.size().area() as u32;
+ Pixel {
+ r: (r / area) as i16,
+ g: (g / area) as i16,
+ b: (b / area) as i16,
+ }
+}
diff --git a/bv1/codec/src/frameio.rs b/bv1/codec/src/frameio.rs
new file mode 100644
index 0000000..f6cbcbf
--- /dev/null
+++ b/bv1/codec/src/frameio.rs
@@ -0,0 +1,35 @@
+use crate::{Frame, Pixel, PixelValue, P2};
+use std::io::{Read, Result, Write};
+
+impl Frame {
+ pub fn read(inp: &mut impl Read, size: P2) -> Result<Frame> {
+ let mut f = Frame::new(size);
+
+ for y in 0..size.y {
+ for x in 0..size.x {
+ let mut cc = [0u8; 3];
+ inp.read_exact(&mut cc)?;
+ f[P2 { x, y }] = Pixel {
+ r: cc[0] as PixelValue,
+ g: cc[1] as PixelValue,
+ b: cc[2] as PixelValue,
+ };
+ }
+ }
+ Ok(f)
+ }
+
+ pub fn write(out: &mut impl Write, frame: &Frame) -> Result<()> {
+ for y in 0..frame.size.y {
+ for x in 0..frame.size.x {
+ let p = frame[P2 { x, y }];
+ let mut cc = [0u8; 3];
+ cc[0] = p.r.clamp(0, 255) as u8;
+ cc[1] = p.g.clamp(0, 255) as u8;
+ cc[2] = p.b.clamp(0, 255) as u8;
+ out.write_all(&mut cc)?;
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/bv1/codec/src/huff.rs b/bv1/codec/src/huff.rs
new file mode 100644
index 0000000..6d74c42
--- /dev/null
+++ b/bv1/codec/src/huff.rs
@@ -0,0 +1,190 @@
+use std::io::{Read, Result, Write};
+
+#[derive(Debug, Clone)]
+enum HT {
+ Branch(Box<HT>, Box<HT>),
+ Terminal(u8),
+}
+
+pub fn write_huff(buf: &[u8], w: &mut impl Write) -> Result<usize> {
+ let mut w = BitIO::new(w);
+ assert!(buf.len() <= 0xffffff, "huff frame too big");
+ w.wbyte((buf.len() & 0xff) as u8)?;
+ w.wbyte(((buf.len() >> 8) & 0xff) as u8)?;
+ w.wbyte(((buf.len() >> 16) & 0xff) as u8)?;
+
+ let mut probs = [0usize; 256];
+ for b in buf {
+ probs[*b as usize] += 1;
+ }
+ let tree = HT::from_probabilities(probs);
+ let mut table = [0u32; 256];
+ tree.create_lut(&mut table, 1);
+ tree.write(&mut w)?;
+
+
+ for b in buf {
+ let mut k = table[*b as usize];
+ while k != 1 {
+ w.wbit((k & 1) == 1)?;
+ k >>= 1;
+ }
+ }
+
+ w.flush()?;
+ Ok(w.position)
+}
+
+pub fn read_huff(r: &mut impl Read, buf: &mut Vec<u8>) -> Result<usize> {
+ let mut r = BitIO::new(r);
+
+ let mut len = 0usize;
+ len |= r.rbyte()? as usize;
+ len |= (r.rbyte()? as usize) << 8;
+ len |= (r.rbyte()? as usize) << 16;
+
+ let root = HT::read(&mut r)?;
+ let root = match &root {
+ HT::Branch(a, b) => [a, b],
+ _ => panic!("no!"),
+ };
+
+ let mut cursor = root;
+ while buf.len() != len {
+ let v = r.rbit()?;
+ match cursor[v as usize].as_ref() {
+ HT::Branch(a, b) => {
+ cursor = [a, b];
+ }
+ HT::Terminal(n) => {
+ buf.push(*n);
+ cursor = root;
+ }
+ }
+ }
+ Ok(r.position)
+}
+
+impl HT {
+ pub fn from_probabilities(ps: [usize; 256]) -> Self {
+ let mut parts = ps
+ .into_iter()
+ .enumerate()
+ .map(|(n, p)| (p, HT::Terminal(n as u8)))
+ .collect::<Vec<_>>();
+
+ while parts.len() != 1 {
+ parts.sort_by_key(|e| -(e.0 as isize));
+ let ((ap, at), (bp, bt)) = (parts.pop().unwrap(), parts.pop().unwrap());
+ parts.push((ap + bp + 1, HT::Branch(box at, box bt)))
+ }
+ parts[0].1.clone()
+ }
+ pub fn create_lut(&self, table: &mut [u32; 256], mut prefix: u32) {
+ assert!(self.depth() < 30, "too deep! doesnt fit {}", self.depth());
+ match self {
+ HT::Branch(a, b) => {
+ let pz = 32 - prefix.leading_zeros();
+ prefix ^= 1 << pz;
+ prefix ^= 1 << (pz - 1);
+ a.create_lut(table, prefix);
+ prefix ^= 1 << (pz - 1);
+ b.create_lut(table, prefix);
+ }
+ HT::Terminal(n) => {
+ assert_eq!(table[*n as usize], 0);
+ table[*n as usize] = prefix
+ }
+ }
+ }
+ pub fn depth(&self) -> usize {
+ match self {
+ HT::Branch(a, b) => a.depth().max(b.depth()) + 1,
+ HT::Terminal(_) => 0,
+ }
+ }
+ pub fn write(&self, w: &mut BitIO<impl Write>) -> Result<()> {
+ match self {
+ HT::Branch(a, b) => {
+ w.wbit(false)?;
+ a.write(w)?;
+ b.write(w)?;
+ }
+ HT::Terminal(n) => {
+ w.wbit(true)?;
+ w.wbyte(*n)?;
+ }
+ }
+ Ok(())
+ }
+ pub fn read(r: &mut BitIO<impl Read>) -> Result<Self> {
+ match r.rbit()? {
+ false => Ok(Self::Branch(box Self::read(r)?, box Self::read(r)?)),
+ true => Ok(Self::Terminal(r.rbyte()?)),
+ }
+ }
+}
+
+pub struct BitIO<T> {
+ inner: T,
+ byte: u8,
+ position: usize,
+}
+impl<T> BitIO<T> {
+ pub fn new(inner: T) -> Self {
+ Self {
+ inner,
+ byte: 0,
+ position: 0,
+ }
+ }
+}
+impl<T: Write> BitIO<T> {
+ #[inline]
+ pub fn wbit(&mut self, b: bool) -> Result<()> {
+ self.byte <<= 1;
+ self.byte |= b as u8;
+ self.position += 1;
+ if self.position & 0b111 == 0 {
+ self.inner.write_all(&[self.byte])?;
+ }
+ Ok(())
+ }
+ #[inline]
+ pub fn wbyte(&mut self, v: u8) -> Result<()> {
+ for i in 0..8 {
+ self.wbit((v & (1 << i)) != 0)?;
+ }
+ Ok(())
+ }
+ pub fn flush(&mut self) -> Result<()> {
+ while self.position & 0b111 != 0 {
+ self.wbit(false)?;
+ }
+ Ok(())
+ }
+}
+
+impl<T: Read> BitIO<T> {
+ #[inline]
+ pub fn rbit(&mut self) -> Result<bool> {
+ if self.position & 0b111 == 0 {
+ let mut buf = [0];
+ self.inner.read_exact(&mut buf)?;
+ self.byte = buf[0];
+ }
+
+ let v = (self.byte & 0b10000000) != 0;
+ self.byte <<= 1;
+ self.position += 1;
+ Ok(v)
+ }
+ #[inline]
+ pub fn rbyte(&mut self) -> Result<u8> {
+ let mut v = 0u8;
+ for i in 0..8 {
+ v |= (self.rbit()? as u8) << i;
+ }
+ Ok(v)
+ }
+}
diff --git a/bv1/codec/src/impls.rs b/bv1/codec/src/impls.rs
new file mode 100644
index 0000000..b4cc119
--- /dev/null
+++ b/bv1/codec/src/impls.rs
@@ -0,0 +1,165 @@
+use crate::{Frame, Pixel, Ref, View, P2};
+use std::ops::{Add, AddAssign, Index, IndexMut, Sub};
+
+#[cfg(feature = "parallel")]
+pub use rayon::join;
+#[cfg(not(feature = "parallel"))]
+pub fn join<A, B, RA, RB>(oper_a: A, oper_b: B) -> (RA, RB)
+where
+ A: FnOnce() -> RA + Send,
+ B: FnOnce() -> RB + Send,
+ RA: Send,
+ RB: Send,
+{
+ (oper_a(), oper_b())
+}
+
+impl Frame {
+ pub fn export(&self, view: View) -> Vec<Pixel> {
+ let mut o = vec![];
+ for y in view.a.y..view.b.y {
+ for x in view.a.x..view.b.x {
+ o.push(self[P2 { x, y }])
+ }
+ }
+ o
+ }
+ pub fn import(&mut self, view: View, mut source: &[Pixel]) {
+ for y in view.a.y..view.b.y {
+ for x in view.a.x..view.b.x {
+ self[P2 { x, y }] = source[0];
+ source = &source[1..];
+ }
+ }
+ assert_eq!(source.len(), 0)
+ }
+ pub fn new(size: P2) -> Self {
+ Self {
+ pixels: vec![Pixel::default(); size.area()],
+ size,
+ }
+ }
+}
+impl Ref {
+ #[inline]
+ pub fn apply<F: Fn(&mut Self)>(mut self, f: F) -> Self {
+ f(&mut self);
+ self
+ }
+}
+impl Pixel {
+ pub const BLACK: Pixel = Pixel { r: 0, g: 0, b: 0 };
+ pub const RED: Pixel = Pixel { r: 255, g: 0, b: 0 };
+ pub const GREEN: Pixel = Pixel { r: 0, g: 255, b: 0 };
+ pub const BLUE: Pixel = Pixel { r: 0, g: 0, b: 255 };
+}
+impl AddAssign for P2 {
+ fn add_assign(&mut self, rhs: Self) {
+ self.x += rhs.x;
+ self.y += rhs.y;
+ }
+}
+impl Add for Pixel {
+ type Output = Pixel;
+ #[inline]
+ fn add(self, rhs: Self) -> Self::Output {
+ Self {
+ r: self.r + rhs.r,
+ g: self.g + rhs.g,
+ b: self.b + rhs.b,
+ }
+ }
+}
+impl Sub for Pixel {
+ type Output = Pixel;
+ #[inline]
+ fn sub(self, rhs: Self) -> Self::Output {
+ Self {
+ r: self.r - rhs.r,
+ g: self.g - rhs.g,
+ b: self.b - rhs.b,
+ }
+ }
+}
+impl P2 {
+ pub const ZERO: P2 = P2 { x: 0, y: 0 };
+ pub const X: P2 = P2 { x: 1, y: 0 };
+ pub const Y: P2 = P2 { x: 0, y: 1 };
+
+ #[inline]
+ pub fn area(&self) -> usize {
+ (self.x * self.y) as usize
+ }
+}
+impl View {
+ #[inline]
+ pub fn all(b: P2) -> Self {
+ Self {
+ a: P2::default(),
+ b,
+ }
+ }
+ #[inline]
+ pub fn size(&self) -> P2 {
+ self.b - self.a
+ }
+}
+impl Add<P2> for View {
+ type Output = View;
+ #[inline]
+ fn add(self, rhs: P2) -> Self::Output {
+ View {
+ a: self.a + rhs,
+ b: self.b + rhs,
+ }
+ }
+}
+impl Add for P2 {
+ type Output = P2;
+ #[inline]
+ fn add(self, rhs: Self) -> Self::Output {
+ Self {
+ x: self.x + rhs.x,
+ y: self.y + rhs.y,
+ }
+ }
+}
+impl Sub for P2 {
+ type Output = P2;
+ #[inline]
+ fn sub(self, rhs: Self) -> Self::Output {
+ Self {
+ x: self.x - rhs.x,
+ y: self.y - rhs.y,
+ }
+ }
+}
+
+impl Index<P2> for Frame {
+ type Output = Pixel;
+ #[inline]
+ fn index(&self, P2 { x, y }: P2) -> &Self::Output {
+ &self
+ .pixels
+ .get(x as usize + (y as usize * self.size.x as usize))
+ .unwrap_or(&Pixel { r: 0, g: 0, b: 0 })
+ }
+}
+impl IndexMut<P2> for Frame {
+ #[inline]
+ fn index_mut(&mut self, P2 { x, y }: P2) -> &mut Self::Output {
+ &mut self.pixels[x as usize + (y as usize * self.size.x as usize)]
+ }
+}
+
+pub trait ToArray {
+ type Output;
+ fn to_array(self) -> [Self::Output; 2];
+}
+impl<A> ToArray for (A, A) {
+ type Output = A;
+ #[inline]
+ fn to_array(self) -> [A; 2] {
+ [self.0, self.1]
+ }
+}
diff --git a/bv1/codec/src/lib.rs b/bv1/codec/src/lib.rs
new file mode 100644
index 0000000..c764211
--- /dev/null
+++ b/bv1/codec/src/lib.rs
@@ -0,0 +1,55 @@
+#![feature(portable_simd)]
+#![feature(io_error_other)]
+#![feature(box_syntax)]
+
+pub mod debug;
+pub mod decode;
+pub mod diff;
+pub mod encode;
+pub mod frameio;
+pub mod huff;
+pub mod impls;
+pub mod serialize;
+pub mod split;
+
+pub type PixelValue = i16;
+
+pub use decode::{decode, Decoder};
+pub use encode::encode;
+
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+pub struct Pixel {
+ pub r: PixelValue,
+ pub g: PixelValue,
+ pub b: PixelValue,
+}
+
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+pub struct P2 {
+ pub x: i32,
+ pub y: i32,
+}
+
+pub struct Frame {
+ pub size: P2,
+ pub pixels: Vec<Pixel>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct View {
+ pub a: P2,
+ pub b: P2,
+}
+
+#[derive(Debug, Clone)]
+pub enum Block {
+ Split(Box<Block>, Box<Block>),
+ Lit(Vec<Pixel>),
+ Ref(Ref),
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct Ref {
+ pub pos_off: P2,
+ pub color_off: Pixel,
+}
diff --git a/bv1/codec/src/serialize.rs b/bv1/codec/src/serialize.rs
new file mode 100644
index 0000000..960aa1b
--- /dev/null
+++ b/bv1/codec/src/serialize.rs
@@ -0,0 +1,116 @@
+use crate::{split::split, Block, Pixel, Ref, View, P2};
+use std::io::{Read, Result, Write};
+
+impl Pixel {
+ #[inline]
+ pub fn write(&self, w: &mut impl Write) -> Result<()> {
+ w.write_all(&[
+ self.r.clamp(0, 255) as u8,
+ self.g.clamp(0, 255) as u8,
+ self.b.clamp(0, 255) as u8,
+ ])
+ }
+ #[inline]
+ pub fn read(r: &mut impl Read) -> Result<Pixel> {
+ Ok(Self {
+ r: read_byte(r)? as i16,
+ g: read_byte(r)? as i16,
+ b: read_byte(r)? as i16,
+ })
+ }
+}
+impl Pixel {
+ #[inline]
+ pub fn write_full(&self, w: &mut impl Write) -> Result<()> {
+ write_word(w, self.r)?;
+ write_word(w, self.g)?;
+ write_word(w, self.b)?;
+ Ok(())
+ }
+ #[inline]
+ pub fn read_full(r: &mut impl Read) -> Result<Pixel> {
+ Ok(Self {
+ r: read_word(r)?,
+ g: read_word(r)?,
+ b: read_word(r)?,
+ })
+ }
+}
+impl P2 {
+ #[inline]
+ pub fn write(&self, w: &mut impl Write) -> Result<()> {
+ write_word(w, self.x as i16)?;
+ write_word(w, self.y as i16)?;
+ Ok(())
+ }
+
+ #[inline]
+ pub fn read(r: &mut impl Read) -> Result<P2> {
+ Ok(Self {
+ x: read_word(r)? as i32,
+ y: read_word(r)? as i32,
+ })
+ }
+}
+
+impl Block {
+ pub fn write(&self, w: &mut impl Write) -> Result<()> {
+ match self {
+ Block::Split(a, b) => {
+ w.write_all(&[0])?;
+ a.write(w)?;
+ b.write(w)?;
+ }
+ Block::Lit(pixels) => {
+ w.write_all(&[1])?;
+ for p in pixels {
+ p.write(w)?;
+ }
+ }
+ Block::Ref(k) => {
+ w.write_all(&[2])?;
+ k.pos_off.write(w)?;
+ k.color_off.write_full(w)?;
+ }
+ }
+ Ok(())
+ }
+ pub fn read(r: &mut impl Read, view: View) -> Result<Block> {
+ match read_byte(r)? {
+ 0 => {
+ let [av, bv] = split(view);
+ Ok(Block::Split(
+ box Block::read(r, av)?,
+ box Block::read(r, bv)?,
+ ))
+ }
+ 1 => {
+ let mut px = vec![];
+ for _ in 0..view.size().area() {
+ px.push(Pixel::read(r)?)
+ }
+ Ok(Block::Lit(px))
+ }
+ 2 => Ok(Block::Ref(Ref {
+ pos_off: P2::read(r)?,
+ color_off: Pixel::read_full(r)?,
+ })),
+ _ => Err(std::io::Error::other("unknown block variant")),
+ }
+ }
+}
+
+#[inline]
+fn read_byte(r: &mut impl Read) -> Result<u8> {
+ let mut buf = [0u8];
+ r.read_exact(&mut buf)?;
+ Ok(buf[0])
+}
+#[inline]
+fn write_word(w: &mut impl Write, v: i16) -> Result<()> {
+ w.write_all(&[(v & 0xff) as u8, (v >> 8) as u8])
+}
+#[inline]
+fn read_word(r: &mut impl Read) -> Result<i16> {
+ Ok((read_byte(r)? as u16 | ((read_byte(r)? as u16) << 8)) as i16)
+}
diff --git a/bv1/codec/src/split.rs b/bv1/codec/src/split.rs
new file mode 100644
index 0000000..c17179e
--- /dev/null
+++ b/bv1/codec/src/split.rs
@@ -0,0 +1,42 @@
+use crate::{View, P2};
+
+pub fn split(view: View) -> [View; 2] {
+ let s = view.size();
+ if s.x > s.y {
+ let mid_x = (view.a.x + view.b.x) / 2;
+ [
+ View {
+ a: view.a,
+ b: P2 {
+ x: mid_x,
+ y: view.b.y,
+ },
+ },
+ View {
+ a: P2 {
+ x: mid_x,
+ y: view.a.y,
+ },
+ b: view.b,
+ },
+ ]
+ } else {
+ let mid_y = (view.a.y + view.b.y) / 2;
+ [
+ View {
+ a: view.a,
+ b: P2 {
+ x: view.b.x,
+ y: mid_y,
+ },
+ },
+ View {
+ a: P2 {
+ x: view.a.x,
+ y: mid_y,
+ },
+ b: view.b,
+ },
+ ]
+ }
+}
diff --git a/bv1/tools/d-disp b/bv1/tools/d-disp
new file mode 100755
index 0000000..d7b8feb
--- /dev/null
+++ b/bv1/tools/d-disp
@@ -0,0 +1,2 @@
+#!/bin/fish
+cargo run --release -- -W 1920 -H 1080 decode | ffplay -loglevel quiet -video_size 1920x1080 -pixel_format rgb24 -f rawvideo pipe:0 \ No newline at end of file
diff --git a/bv1/tools/d-dispd b/bv1/tools/d-dispd
new file mode 100755
index 0000000..97a2b90
--- /dev/null
+++ b/bv1/tools/d-dispd
@@ -0,0 +1,2 @@
+#!/bin/fish
+cargo run --release -- -W 1920 -H 1080 decode --debug | ffplay -loglevel quiet -video_size 1920x1080 -pixel_format rgb24 -f rawvideo pipe:0 \ No newline at end of file
diff --git a/bv1/tools/d-save b/bv1/tools/d-save
new file mode 100755
index 0000000..1f19994
--- /dev/null
+++ b/bv1/tools/d-save
@@ -0,0 +1,2 @@
+#!/bin/fish
+cargo run --release -- -W 1920 -H 1080 decode | ffmpeg -pixel_format rgb24 -video_size 1920x1080 -framerate 30 -f rawvideo -i pipe:0 -i $argv[1] -map '0:v' -map '1:a' -c:v libsvtav1 -y data/output.webm
diff --git a/bv1/tools/d-saved b/bv1/tools/d-saved
new file mode 100755
index 0000000..16de45a
--- /dev/null
+++ b/bv1/tools/d-saved
@@ -0,0 +1,2 @@
+#!/bin/fish
+cargo run --release -- -W 1920 -H 1080 decode --debug | ffmpeg -pixel_format rgb24 -video_size 1920x1080 -framerate 30 -f rawvideo -i pipe:0 -i $argv[1] -map '0:v' -map '1:a' -c:v libsvtav1 -y data/output-debug.webm
diff --git a/bv1/tools/e b/bv1/tools/e
new file mode 100755
index 0000000..a85a767
--- /dev/null
+++ b/bv1/tools/e
@@ -0,0 +1,2 @@
+#!/bin/fish
+ffmpeg -loglevel quiet -i $argv[1] -vf format=rgb24 -f rawvideo pipe:1 | cargo run --release -- -W 1920 -H 1080 encode $argv[2..]