Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace img_hash dependency #725

Merged
merged 1 commit into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ windows-sys = { version = "0.48.0", features = [
libc = { version = "0.2.101", optional = true }

[dev-dependencies]
img_hash = "3.1.0"
seahash = "4.1.0"

[target.'cfg(windows)'.dev-dependencies]
sysinfo = { version = "0.29.10", default-features = false }

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = "0.5.0"
Expand Down
4 changes: 3 additions & 1 deletion crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ wasmtime = { version = "13.0.0", default-features = false, features = [
"cranelift",
"parallel-compilation",
] }
wasmtime-wasi = "13.0.0"
wasmtime-wasi = { version = "13.0.0", default-features = false, features = [
"sync",
] }
wasi-common = "13.0.0"

[target.'cfg(windows)'.dependencies]
Expand Down
8 changes: 2 additions & 6 deletions src/auto_splitting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,12 +581,8 @@ async fn run(
Request::ReloadScript(ret) => {
let mut config = Config::default();
config.settings_store = Some(runtime.settings_store().clone());

match ScriptRuntime::new(
&script_path,
Timer(timer.clone()),
config,
) {

match ScriptRuntime::new(&script_path, Timer(timer.clone()), config) {
Ok(r) => {
ret.send(Ok(())).ok();
runtime = r;
Expand Down
133 changes: 55 additions & 78 deletions tests/rendering.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#![cfg(feature = "software-rendering")]
#![cfg(all(
feature = "software-rendering",
not(all(target_arch = "x86", not(target_feature = "sse"))),
))]

mod layout_files;
mod run_files;
#[path = "../src/util/tests_helper.rs"]
mod tests_helper;

use image::Rgba;
use img_hash::{HasherConfig, ImageHash};
use livesplit_core::{
component::{self, timer},
layout::{self, Component, ComponentState, Layout, LayoutDirection, LayoutState},
Expand Down Expand Up @@ -38,26 +40,28 @@ fn default() {

let state = layout.state(&timer.snapshot());

check(&state, "luIAAABAPLM=", "default");
check(&state, "670e0e09bf3dbfed", "default");
}

// Font fallback inherently requires fonts from the operating system to
// work. On Windows we have a consistent set of fonts installed for all the
// different languages. We could do the same check on macOS and possibly a
// few other operating systems, which also provide a consistent set of
// fonts, but with a different hash. On Linux however you may have a
// different set of fonts installed, or possibly even none at all, so we
// can't do the same check there.
#[cfg(all(feature = "font-loading", windows))]
#[test]
fn font_fallback() {
// This list is based on the most commonly used writing systems in the
// world:
// https://en.wikipedia.org/wiki/List_of_writing_systems#List_of_writing_systems_by_adoption

let mut run = tests_helper::create_run(&[
// FIXME: Unfortunately we can't use emojis because the vendors like to
// update the look of the emojis every now and then. So for example the
// emojis changed between Windows 10 and 11. We'd have to either detect
// the version of the emoji font or would have to detect the operating
// system version. I believe the latter is something they plan on adding
// into std, so maybe we can eventually use that.
use sysinfo::SystemExt;

let mut run = tests_helper::create_run(&[
// Emoji
// "❤✔👌🤔😂😁🎉💀🤣",

"❤✔👌🤔😂😁🎉💀🤣",
// Braille
"⠃⠗⠁⠊⠇⠇⠑",
// Hebrew
Expand All @@ -84,8 +88,6 @@ fn font_fallback() {
"ไทย",
// Burmese
"မြန်မာ",
// Canadian Aboriginal Syllabics
"ᖃᓂᐅᔮᖅᐸᐃᑦ ᒐᐦᑲᓯᓇᐦᐃᑫᐤ ᑯᖾᖹ ᖿᐟᖻ ᓱᖽᐧᖿ ᑐᑊᘁᗕᑋᗸ",
// Hanzi, Kana
"汉字 漢字 かな カナ",
]);
Expand All @@ -100,15 +102,16 @@ fn font_fallback() {

let _state = layout.state(&timer.snapshot());

// Font fallback inherently requires fonts from the operating system to
// work. On Windows we have a consistent set of fonts installed for all the
// different languages. We could do the same check on macOS and possibly a
// few other operating systems, which also provide a consistent set of
// fonts, but with a different hash. On Linux however you may have a
// different set of fonts installed, or possibly even none at all, so we
// can't do the same check there.
#[cfg(all(feature = "font-loading", windows))]
check(&_state, "zeSAgJgEMbI=", "font_fallback");
let system = sysinfo::System::new();
let build_number: u64 = system.kernel_version().unwrap().parse().unwrap();
let expected_hash = if build_number >= 22000 {
// Windows 11
"d16b447322881767"
} else {
// Windows 10
"f4bffc6bc6fab953"
};
check(&_state, expected_hash, "font_fallback");
}

#[test]
Expand All @@ -119,7 +122,7 @@ fn actual_split_file() {

check(
&layout.state(&timer.snapshot()),
"jMDAARBAPLM=",
"cd9735cf9575f503",
"actual_split_file",
);
}
Expand All @@ -133,7 +136,7 @@ fn wsplit() {
check_dims(
&layout.state(&timer.snapshot()),
[250, 300],
"j/n8/PnZv/c=",
"9c69454a9258e768",
"wsplit",
);
}
Expand All @@ -149,7 +152,7 @@ fn timer_delta_background() {
check_dims(
&layout.state(&timer.snapshot()),
[250, 300],
"a+nRyKBfXc0=",
"fc8e7890593f9da6",
"timer_delta_background_ahead",
);

Expand All @@ -158,7 +161,7 @@ fn timer_delta_background() {
check_dims(
&layout.state(&timer.snapshot()),
[250, 300],
"a+nZyaFfX80=",
"bc5b8383ebb556b8",
"timer_delta_background_stopped",
);
}
Expand All @@ -176,9 +179,14 @@ fn all_components() {

let state = layout.state(&timer.snapshot());

check_dims(&state, [300, 800], "4en3ocnJp/E=", "all_components");
check_dims(&state, [300, 800], "e4db4770453a6d06", "all_components");

check_dims(&state, [150, 800], "SXfHSWVpRkc=", "all_components_thin");
check_dims(
&state,
[150, 800],
"0ecd0bad25453ff6",
"all_components_thin",
);
}

#[test]
Expand All @@ -197,7 +205,7 @@ fn score_split() {
state.components.push(ComponentState::Timer(timer_state));
state.components.push(prev_seg);

check_dims(&state, [300, 400], "jOCAAQTABjc=", "score_split");
check_dims(&state, [300, 400], "6ec6913f5ace6ab6", "score_split");
}

#[test]
Expand All @@ -208,7 +216,7 @@ fn dark_layout() {

check(
&layout.state(&timer.snapshot()),
"T8AQQABqwYc=",
"a47c590792c1bab5",
"dark_layout",
);
}
Expand All @@ -228,7 +236,7 @@ fn subsplits_layout() {
check_dims(
&layout.state(&timer.snapshot()),
[300, 800],
"8/vz8/Pz/+c=",
"57165de23ce37b9c",
"subsplits_layout",
);
}
Expand All @@ -251,7 +259,7 @@ fn display_two_rows() {
check_dims(
&layout.state(&timer.snapshot()),
[200, 100],
"Q0UaMs1J0sA=",
"d174c2f9a0c54d66",
"display_two_rows",
);
}
Expand All @@ -274,7 +282,7 @@ fn single_line_title() {
check_dims(
&layout.state(&timer.snapshot()),
[300, 60],
"ABJtmxt4YZA=",
"5f0a41091c33ecad",
"single_line_title",
);
}
Expand Down Expand Up @@ -308,74 +316,44 @@ fn horizontal() {
check_dims(
&layout.state(&timer.snapshot()),
[1500, 40],
"YnJjcnJSUmM=",
"987157e649936cbb",
"horizontal",
);
}

fn get_comparison_tolerance() -> u32 {
// Without MMX the floating point calculations don't follow IEEE 754, so the tests require a
// tolerance that is greater than 0.
// FIXME: We use SSE as an approximation for the cfg because MMX isn't supported by Rust yet.
if cfg!(all(target_arch = "x86", not(target_feature = "sse"))) {
3
} else {
0
}
}

#[track_caller]
fn check(state: &LayoutState, expected_hash_data: &str, name: &str) {
check_dims(state, [300, 500], expected_hash_data, name);
}

#[track_caller]
fn check_dims(
state: &LayoutState,
dims @ [width, height]: [u32; 2],
expected_hash_data: &str,
name: &str,
) {
fn check_dims(state: &LayoutState, dims: [u32; 2], expected_hash: &str, name: &str) {
let mut renderer = Renderer::new();
renderer.render(state, dims);
let hash_image =
img_hash::image::RgbaImage::from_raw(width, height, renderer.into_image_data()).unwrap();
let image =
image::ImageBuffer::<Rgba<u8>, _>::from_raw(width, height, hash_image.as_raw().as_slice())
.unwrap();

let hasher = HasherConfig::with_bytes_type::<[u8; 8]>().to_hasher();

let calculated_hash = hasher.hash_image(&hash_image);
let calculated_hash_data = calculated_hash.to_base64();
let expected_hash = ImageHash::<[u8; 8]>::from_base64(expected_hash_data).unwrap();
let distance = calculated_hash.dist(&expected_hash);
let hash_image = renderer.image();
let calculated_hash = seahash::hash(&hash_image);
let calculated_hash = format!("{calculated_hash:016x}");

let mut path = PathBuf::from_iter(["target", "renders"]);
fs::create_dir_all(&path).ok();

let mut actual_path = path.clone();
actual_path.push(format!(
"{name}_{}.png",
calculated_hash_data.replace('/', "-"),
));
image.save(&actual_path).ok();
actual_path.push(format!("{name}_{calculated_hash}.png"));
hash_image.save(&actual_path).ok();

if distance > get_comparison_tolerance() {
if calculated_hash != expected_hash {
path.push("diff");
fs::create_dir_all(&path).ok();
path.pop();

let mut expected_path = path.clone();
expected_path.push(format!(
"{name}_{}.png",
expected_hash_data.replace('/', "-"),
));
expected_path.push(format!("{name}_{expected_hash}.png"));
let diff_path = if let Ok(expected_image) = image::open(&expected_path) {
let mut expected_image = expected_image.to_rgba8();
for (x, y, Rgba([r, g, b, a])) in expected_image.enumerate_pixels_mut() {
if x < hash_image.width() && y < hash_image.height() {
let img_hash::image::Rgba([r2, g2, b2, a2]) = *hash_image.get_pixel(x, y);
let image::Rgba([r2, g2, b2, a2]) = *hash_image.get_pixel(x, y);
*r = r.abs_diff(r2);
*g = g.abs_diff(g2);
*b = b.abs_diff(b2);
Expand All @@ -394,10 +372,9 @@ fn check_dims(

panic!(
"Render mismatch for {name}
expected: {expected_hash_data} {}
actual: {calculated_hash_data} {}
diff: {}
distance: {distance}",
expected: {expected_hash} {}
actual: {calculated_hash} {}
diff: {}",
expected_path.display(),
actual_path.display(),
diff_path.display(),
Expand Down
Loading