1
Fork 0

Got score select recognition to work

Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
prescientmoon 2024-08-01 15:41:20 +02:00
parent 6556e81433
commit 0c90628c9d
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
5 changed files with 101 additions and 39 deletions

View file

@ -32,6 +32,11 @@ impl Color {
Self::from_rgba_int((i << 8) + 0xff) 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] #[inline]
pub fn alpha(mut self, a: u8) -> Self { pub fn alpha(mut self, a: u8) -> Self {
self.3 = a; self.3 = a;

View file

@ -74,11 +74,8 @@ pub async fn magic(
for (i, file) in files.iter().enumerate() { for (i, file) in files.iter().enumerate() {
if let Some(_) = file.dimensions() { if let Some(_) = file.dimensions() {
// {{{ Image pre-processing // {{{ Image pre-processing
// Download image and guess it's format
let bytes = file.download().await?; let bytes = file.download().await?;
let format = image::guess_format(&bytes)?; let image = image::load_from_memory(&bytes)?;
let image = image::load_from_memory_with_format(&bytes, format)?;
let mut image = image.resize(1024, 1024, FilterType::Nearest); let mut image = image.resize(1024, 1024, FilterType::Nearest);
// }}} // }}}
// {{{ Detection // {{{ Detection
@ -241,7 +238,7 @@ Title error: {:?}
handle.edit(ctx, edited).await?; handle.edit(ctx, edited).await?;
let score_possibilities = 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 // {{{ OCR error handling
Err(err) => { Err(err) => {
error_with_image( error_with_image(

View file

@ -77,11 +77,11 @@ pub async fn best(
let play = query_as!( let play = query_as!(
DbPlay, DbPlay,
" "
SELECT * FROM plays SELECT * FROM plays
WHERE user_id=? WHERE user_id=?
AND chart_id=? AND chart_id=?
ORDER BY score DESC ORDER BY score DESC
", ",
user.id, user.id,
chart.id chart.id
) )

View file

@ -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 { for y in rect.y..rect.y + rect.height as i32 {
let skew = (shear * ((y - center.1) as f32)) as i32; let skew = (shear * ((y - center.1) as f32)) as i32;
for i in rect.x..rect.x + width { 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) { if unsigned_in_bounds(image, x, y) {
let pixel = image.get_pixel(x as u32, y as u32); 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 /// Performs a horizontal shear operation, without performing anti-aliasing
pub fn yshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32) { pub fn yshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32) {
let height = rect.height as i32; 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; let skew = (shear * ((x - center.0) as f32)) as i32;
for i in rect.y..rect.y + height { 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) { if unsigned_in_bounds(image, x, y) {
let pixel = image.get_pixel(x as u32, y as u32); 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 /// Performs a rotation as a series of three shear operations
/// Does not perform anti-aliasing. /// Does not perform anti-aliasing.
pub fn rotate(image: &mut DynamicImage, rect: Rect, center: Position, angle: f32) { 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); let beta = f32::sin(angle);
xshear(image, rect, center, alpha); xshear(image, rect, center, alpha);
yshear(image, rect, center, beta); yshear(image, rect, center, beta);

View file

@ -12,6 +12,7 @@ use num::{traits::Euclid, Rational64};
use poise::serenity_prelude::{ use poise::serenity_prelude::{
Attachment, AttachmentId, CreateAttachment, CreateEmbed, CreateEmbedAuthor, Timestamp, Attachment, AttachmentId, CreateAttachment, CreateEmbed, CreateEmbedAuthor, Timestamp,
}; };
use sqlx::{query_as, SqlitePool};
use tesseract::{PageSegMode, Tesseract}; use tesseract::{PageSegMode, Tesseract};
use crate::bitmap::{Color, Rect}; use crate::bitmap::{Color, Rect};
@ -602,10 +603,10 @@ impl Play {
.field("ξ-Grade", format!("{}", self.zeta_score.grade()), true) .field("ξ-Grade", format!("{}", self.zeta_score.grade()), true)
.field( .field(
"Status", "Status",
self.status(chart).unwrap_or("?".to_string()), self.status(chart).unwrap_or("-".to_string()),
true, true,
) )
.field("Max recall", "?", true) .field("Max recall", "", true)
.field("ID", format!("{}", self.id), true); .field("ID", format!("{}", self.id), true);
if icon_attachement.is_some() { if icon_attachement.is_some() {
@ -628,6 +629,37 @@ impl Play {
Ok((embed, icon_attachement)) Ok((embed, icon_attachement))
} }
// }}} // }}}
// {{{ Get best play
pub async fn best_play(
db: &SqlitePool,
user: User,
song: Song,
chart: Chart,
) -> Result<Self, Error> {
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 // {{{ Tests
@ -918,8 +950,8 @@ fn widen_by(rects: &mut Vec<RelativeRect>, x: f32, y: f32) {
} }
} }
// }}} // }}}
// {{{ Score // {{{ Score (score screen)
fn score_rects() -> &'static [RelativeRect] { fn score_score_screen_rects() -> &'static [RelativeRect] {
static CELL: OnceLock<Vec<RelativeRect>> = OnceLock::new(); static CELL: OnceLock<Vec<RelativeRect>> = OnceLock::new();
CELL.get_or_init(|| { CELL.get_or_init(|| {
let mut rects: Vec<RelativeRect> = vec![ let mut rects: Vec<RelativeRect> = vec![
@ -939,6 +971,19 @@ fn score_rects() -> &'static [RelativeRect] {
}) })
} }
// }}} // }}}
// {{{ Score (song select)
pub fn score_song_select_rects() -> &'static [RelativeRect] {
static CELL: OnceLock<Vec<RelativeRect>> = OnceLock::new();
CELL.get_or_init(|| {
let mut rects: Vec<RelativeRect> = 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 // {{{ Difficulty
fn difficulty_rects() -> &'static [RelativeRect] { fn difficulty_rects() -> &'static [RelativeRect] {
static CELL: OnceLock<Vec<RelativeRect>> = OnceLock::new(); static CELL: OnceLock<Vec<RelativeRect>> = OnceLock::new();
@ -1128,6 +1173,14 @@ fn difficulty_pixel(difficulty: Difficulty) -> &'static [RelativePoint] {
Difficulty::BYD => byd_etr_pixel(), 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 // {{{ Recognise chart
@ -1276,13 +1329,22 @@ impl ImageCropper {
&mut self, &mut self,
note_count: Option<u32>, note_count: Option<u32>,
image: &DynamicImage, image: &DynamicImage,
kind: ScoreKind,
) -> Result<Vec<Score>, Error> { ) -> Result<Vec<Score>, Error> {
println!("kind {kind:?}");
self.crop_image_to_bytes( self.crop_image_to_bytes(
&image.resize_exact(image.width(), image.height(), FilterType::Nearest), &image.resize_exact(image.width(), image.height(), FilterType::Nearest),
RelativeRect::from_aspect_ratio(ImageDimensions::from_image(image), score_rects()) RelativeRect::from_aspect_ratio(
.ok_or_else(|| "Could not find score area in picture")? ImageDimensions::from_image(image),
.to_absolute() if kind == ScoreKind::ScoreScreen {
.to_rect(), 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![]; let mut results = vec![];
@ -1409,39 +1471,29 @@ impl ImageCropper {
kind: ScoreKind, kind: ScoreKind,
) -> Result<Difficulty, Error> { ) -> Result<Difficulty, Error> {
if kind == ScoreKind::SongSelect { 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 dimensions = ImageDimensions::from_image(image);
let min = colors let min = DIFFICULTY_MENU_PIXEL_COLORS
.iter() .iter()
.zip(Difficulty::DIFFICULTIES) .zip(Difficulty::DIFFICULTIES)
.min_by_key(|(c, d)| { .min_by_key(|(c, d)| {
let points = difficulty_pixel(*d); let points = difficulty_pixel(*d);
let point = RelativePoint::from_aspect_ratio(dimensions, points) let point = RelativePoint::from_aspect_ratio(dimensions, points)
.ok_or_else(|| "Could not find difficulty pixel in picture") .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)) .unwrap_or(RelativePoint::new(0.0, 0.0, dimensions))
.to_absolute(); .to_absolute();
let image_color = image.get_pixel(point.x, point.y); let image_color = image.get_pixel(point.x, point.y);
let image_color = Color( let image_color = Color::from_bytes(image_color.0);
image_color[0],
image_color[1],
image_color[2],
image_color[3],
);
let distance = c.distance(image_color); let distance = c.distance(image_color);
println!("distance {distance} image_color {image_color:?} color {c:?} difficulty {d:?}");
(distance * 10000.0) as u32 (distance * 10000.0) as u32
}); })
.unwrap();
return Ok(min.unwrap().1); return Ok(min.1);
} }
self.crop_image_to_bytes( self.crop_image_to_bytes(