Got score select recognition to work
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
6556e81433
commit
0c90628c9d
|
@ -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;
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
16
src/image.rs
16
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 {
|
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);
|
||||||
|
|
102
src/score.rs
102
src/score.rs
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue