From 0c90628c9d8cdf1b9351a06b431e4e69e6fd17a9 Mon Sep 17 00:00:00 2001 From: prescientmoon Date: Thu, 1 Aug 2024 15:41:20 +0200 Subject: [PATCH] Got score select recognition to work Signed-off-by: prescientmoon --- src/bitmap.rs | 5 +++ src/commands/score.rs | 7 +-- src/commands/stats.rs | 10 ++--- src/image.rs | 16 +++++-- src/score.rs | 102 +++++++++++++++++++++++++++++++----------- 5 files changed, 101 insertions(+), 39 deletions(-) diff --git a/src/bitmap.rs b/src/bitmap.rs index 5783897..7e8158b 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs @@ -32,6 +32,11 @@ impl Color { Self::from_rgba_int((i << 8) + 0xff) } + #[inline] + pub const fn from_bytes(bytes: [u8; 4]) -> Self { + Self(bytes[0], bytes[1], bytes[1], bytes[3]) + } + #[inline] pub fn alpha(mut self, a: u8) -> Self { self.3 = a; diff --git a/src/commands/score.rs b/src/commands/score.rs index 7c09896..3254591 100644 --- a/src/commands/score.rs +++ b/src/commands/score.rs @@ -74,11 +74,8 @@ pub async fn magic( for (i, file) in files.iter().enumerate() { if let Some(_) = file.dimensions() { // {{{ Image pre-processing - // Download image and guess it's format let bytes = file.download().await?; - let format = image::guess_format(&bytes)?; - - let image = image::load_from_memory_with_format(&bytes, format)?; + let image = image::load_from_memory(&bytes)?; let mut image = image.resize(1024, 1024, FilterType::Nearest); // }}} // {{{ Detection @@ -241,7 +238,7 @@ Title error: {:?} handle.edit(ctx, edited).await?; let score_possibilities = - match cropper.read_score(Some(chart.note_count), &ocr_image) { + match cropper.read_score(Some(chart.note_count), &ocr_image, kind) { // {{{ OCR error handling Err(err) => { error_with_image( diff --git a/src/commands/stats.rs b/src/commands/stats.rs index cc13418..b6e89f5 100644 --- a/src/commands/stats.rs +++ b/src/commands/stats.rs @@ -77,11 +77,11 @@ pub async fn best( let play = query_as!( DbPlay, " - SELECT * FROM plays - WHERE user_id=? - AND chart_id=? - ORDER BY score DESC - ", + SELECT * FROM plays + WHERE user_id=? + AND chart_id=? + ORDER BY score DESC + ", user.id, chart.id ) diff --git a/src/image.rs b/src/image.rs index a054219..d1d4250 100644 --- a/src/image.rs +++ b/src/image.rs @@ -12,7 +12,11 @@ pub fn xshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32 for y in rect.y..rect.y + rect.height as i32 { let skew = (shear * ((y - center.1) as f32)) as i32; for i in rect.x..rect.x + width { - let x = if skew < 0 { i } else { rect.x + width - 1 - i }; + let x = if skew < 0 { + i + } else { + 2 * rect.x + width - 1 - i + }; if unsigned_in_bounds(image, x, y) { let pixel = image.get_pixel(x as u32, y as u32); @@ -27,10 +31,14 @@ pub fn xshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32 /// Performs a horizontal shear operation, without performing anti-aliasing pub fn yshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32) { let height = rect.height as i32; - for x in rect.x..rect.x + rect.height as i32 { + for x in rect.x..rect.x + rect.width as i32 { let skew = (shear * ((x - center.0) as f32)) as i32; for i in rect.y..rect.y + height { - let y = if skew < 0 { i } else { rect.y + height - 1 - i }; + let y = if skew < 0 { + i + } else { + 2 * rect.y + height - 1 - i + }; if unsigned_in_bounds(image, x, y) { let pixel = image.get_pixel(x as u32, y as u32); @@ -45,7 +53,7 @@ pub fn yshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32 /// Performs a rotation as a series of three shear operations /// Does not perform anti-aliasing. pub fn rotate(image: &mut DynamicImage, rect: Rect, center: Position, angle: f32) { - let alpha = -f32::tan(angle); + let alpha = -f32::tan(angle / 2.0); let beta = f32::sin(angle); xshear(image, rect, center, alpha); yshear(image, rect, center, beta); diff --git a/src/score.rs b/src/score.rs index c1bc9b5..59dbfd6 100644 --- a/src/score.rs +++ b/src/score.rs @@ -12,6 +12,7 @@ use num::{traits::Euclid, Rational64}; use poise::serenity_prelude::{ Attachment, AttachmentId, CreateAttachment, CreateEmbed, CreateEmbedAuthor, Timestamp, }; +use sqlx::{query_as, SqlitePool}; use tesseract::{PageSegMode, Tesseract}; use crate::bitmap::{Color, Rect}; @@ -602,10 +603,10 @@ impl Play { .field("ξ-Grade", format!("{}", self.zeta_score.grade()), true) .field( "Status", - self.status(chart).unwrap_or("?".to_string()), + self.status(chart).unwrap_or("-".to_string()), true, ) - .field("Max recall", "?", true) + .field("Max recall", "—", true) .field("ID", format!("{}", self.id), true); if icon_attachement.is_some() { @@ -628,6 +629,37 @@ impl Play { Ok((embed, icon_attachement)) } // }}} + // {{{ Get best play + pub async fn best_play( + db: &SqlitePool, + user: User, + song: Song, + chart: Chart, + ) -> Result { + let play = query_as!( + DbPlay, + " + SELECT * FROM plays + WHERE user_id=? + AND chart_id=? + ORDER BY score DESC + ", + user.id, + chart.id + ) + .fetch_one(db) + .await + .map_err(|_| { + format!( + "Could not find any scores for {} [{:?}]", + song.title, chart.difficulty + ) + })? + .to_play(); + + Ok(play) + } + // }}} } // }}} // {{{ Tests @@ -918,8 +950,8 @@ fn widen_by(rects: &mut Vec, x: f32, y: f32) { } } // }}} -// {{{ Score -fn score_rects() -> &'static [RelativeRect] { +// {{{ Score (score screen) +fn score_score_screen_rects() -> &'static [RelativeRect] { static CELL: OnceLock> = OnceLock::new(); CELL.get_or_init(|| { let mut rects: Vec = vec![ @@ -939,6 +971,19 @@ fn score_rects() -> &'static [RelativeRect] { }) } // }}} +// {{{ Score (song select) +pub fn score_song_select_rects() -> &'static [RelativeRect] { + static CELL: OnceLock> = OnceLock::new(); + CELL.get_or_init(|| { + let mut rects: Vec = vec![ + AbsoluteRect::new(95, 256, 278, 49, ImageDimensions::new(2532, 1170)).to_relative(), + AbsoluteRect::new(15, 264, 291, 52, ImageDimensions::new(2160, 1620)).to_relative(), + ]; + process_datapoints(&mut rects); + rects + }) +} +// }}} // {{{ Difficulty fn difficulty_rects() -> &'static [RelativeRect] { static CELL: OnceLock> = OnceLock::new(); @@ -1128,6 +1173,14 @@ fn difficulty_pixel(difficulty: Difficulty) -> &'static [RelativePoint] { Difficulty::BYD => byd_etr_pixel(), } } + +const DIFFICULTY_MENU_PIXEL_COLORS: [Color; Difficulty::DIFFICULTIES.len()] = [ + Color::from_rgb_int(0xAAE5F7), + Color::from_rgb_int(0xBFDD85), + Color::from_rgb_int(0xCB74AB), + Color::from_rgb_int(0xC4B7D3), + Color::from_rgb_int(0xF89AAC), +]; // }}} // }}} // {{{ Recognise chart @@ -1276,13 +1329,22 @@ impl ImageCropper { &mut self, note_count: Option, image: &DynamicImage, + kind: ScoreKind, ) -> Result, Error> { + println!("kind {kind:?}"); self.crop_image_to_bytes( &image.resize_exact(image.width(), image.height(), FilterType::Nearest), - RelativeRect::from_aspect_ratio(ImageDimensions::from_image(image), score_rects()) - .ok_or_else(|| "Could not find score area in picture")? - .to_absolute() - .to_rect(), + RelativeRect::from_aspect_ratio( + ImageDimensions::from_image(image), + if kind == ScoreKind::ScoreScreen { + score_score_screen_rects() + } else { + score_song_select_rects() + }, + ) + .ok_or_else(|| "Could not find score area in picture")? + .to_absolute() + .to_rect(), )?; let mut results = vec![]; @@ -1409,39 +1471,29 @@ impl ImageCropper { kind: ScoreKind, ) -> Result { if kind == ScoreKind::SongSelect { - let colors = [ - Color::BLACK, - Color::BLACK, - Color::from_rgb_int(0xCB74AB), - Color::from_rgb_int(0xC4B7D3), - Color::BLACK, - ]; - let dimensions = ImageDimensions::from_image(image); - let min = colors + let min = DIFFICULTY_MENU_PIXEL_COLORS .iter() .zip(Difficulty::DIFFICULTIES) .min_by_key(|(c, d)| { let points = difficulty_pixel(*d); let point = RelativePoint::from_aspect_ratio(dimensions, points) .ok_or_else(|| "Could not find difficulty pixel in picture") + // SAFETY: should I just throwkkk here? .unwrap_or(RelativePoint::new(0.0, 0.0, dimensions)) .to_absolute(); let image_color = image.get_pixel(point.x, point.y); - let image_color = Color( - image_color[0], - image_color[1], - image_color[2], - image_color[3], - ); + let image_color = Color::from_bytes(image_color.0); let distance = c.distance(image_color); + println!("distance {distance} image_color {image_color:?} color {c:?} difficulty {d:?}"); (distance * 10000.0) as u32 - }); + }) + .unwrap(); - return Ok(min.unwrap().1); + return Ok(min.1); } self.crop_image_to_bytes(