import java.io.File ;
import java.io.IOException ;
import java.io.InputStreamReader ;
import java.nio.file.Path ;
import java.nio.file.Paths ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.List ;
import java.awt.Color ;
import java.util.regex.Pattern ;
import javax.imageio.ImageIO ;
import java.awt.Graphics2D ;
import java.awt.image.BufferedImage ;
public class ArcadeReader {
/ *
* Important data we would like to know for all games :
* Score
* Rank ( Probably implementation - specific )
* Note accuracy [ List ]
* Marvelous
* Perfect
* Great
* Good
* Bad
* OK
* Miss
* Difficulty
* Song Name / Title
* Percentage ( Can be EX Score , a percentage accuracy or survival percent )
* Max Combo
* Other ( Not used by the auto detecter , used for storing misc . data . )
*
* Notes about Readers :
* Love Live
* - Does not use : Marvelous , OK
* Project Diva
* - Does not use : Marvelous , OK
* - Fail condition is MISSxTAKE
* DDR
* - Does not use : Max Combo ( Cannot calculate in combo carryover mode ) , Bad
* - Fail condition is E rank .
* Pop ' n Music
* - Does not use : Marvelous , Bad , OK
* - Stores number of bars filled in Percentage .
* - Fail condition is < 17 in the meter ( max is 24 )
* Sound Voltex
* - Sound Voltex uses the note accuracy list slots as follows :
* Early :
* Error - Marvelous
* Near - Perfect
* Critical - Great
* S - Critical - Good
* Critical - Bad
* Near - OK
* Error - Miss
* - Sound Voltex stores EX score in Percentage .
* - Max Combo is Max Chain
* - Sound Voltex will store additional data about accuracy of note types as such :
* { "chip" : { "scritical" : 0 , "critical" : 0 , "near" : 0 , "error" : 0 } , "long" : { "scritical" : 0 , "error" : 0 } , "vol" : { "scritical" : 0 , "error" : 0 } , "gauge" : < excessive | normal > , "gauge_pct" : 100 }
* //Also storing what type of clear gauge was met and the % of the gauge.
* - Fail condition is < 70 % normal gauge .
* IIDX
* - Not going to support right now .
*
* /
public static void retrieveData ( Path img ) {
new LoveLiveReader ( ) . interpretBoxes ( img ) ;
}
}
class ColorRange {
int r_h , r_l ;
int g_h , g_l ;
int b_h , b_l ;
ColorRange ( int r_l , int r_h , int g_l , int g_h , int b_l , int b_h ) {
this . r_l = r_l ;
this . r_h = r_h ;
this . g_l = g_l ;
this . g_h = g_h ;
this . b_l = b_l ;
this . b_h = b_h ;
}
boolean colorInRange ( Color col ) {
return col . getRed ( ) > = r_l & & col . getRed ( ) < = r_h & & col . getGreen ( ) > = g_l & & col . getGreen ( ) < = g_h & & col . getBlue ( ) > = b_l & & col . getBlue ( ) < = b_h ;
}
}
class Box {
int x , y , w , h ;
boolean ja_required ;
final static int BOX_THRESHOLD = 8 ; //How many pixels outside the specified region the score can be.
Box ( int x , int y , int w , int h ) {
this . x = x ;
this . y = y ;
this . w = w ;
this . h = h ;
}
boolean insideBox ( int x , int y ) {
return this . x - BOX_THRESHOLD < = x & & this . x + this . w + BOX_THRESHOLD > = x & & this . y - BOX_THRESHOLD < = y & & this . y + this . h + BOX_THRESHOLD > = y ;
}
}
class LoveLiveReader extends Reader {
final static int REGION_PADDING = 32 ;
List < Box > extraRegions = new ArrayList < > ( ) ;
static int lastJump = 0 ;
LoveLiveReader ( ) {
readRegions . add ( new Box ( 713 , 401 , 232 , 50 ) ) ; //score[0]
readRegions . add ( new Box ( 613 , 290 , 65 , 36 ) ) ; //rank[1]
extraRegions . add ( new Box ( 65 , 604 , 250 , 53 ) ) ; //perfect outline[0]
extraRegions . add ( new Box ( 65 , 680 , 250 , 53 ) ) ; //great outline[1]
extraRegions . add ( new Box ( 65 , 760 , 250 , 53 ) ) ; //good outline[2]
extraRegions . add ( new Box ( 65 , 840 , 250 , 53 ) ) ; //bad outline[3]
extraRegions . add ( new Box ( 65 , 920 , 250 , 53 ) ) ; //miss outline[4]
readRegions . add ( new Box ( 509 , 604 , 190 , 54 ) ) ; //notes[2]
readRegions . add ( new Box ( 509 , 680 , 190 , 54 ) ) ; //notes[3]
readRegions . add ( new Box ( 509 , 760 , 190 , 54 ) ) ; //notes[4]
readRegions . add ( new Box ( 509 , 840 , 190 , 54 ) ) ; //notes[5]
readRegions . add ( new Box ( 509 , 920 , 190 , 54 ) ) ; //notes[6]
readRegions . add ( new Box ( 26 , 374 , 265 , 36 ) ) ; //difficulty[7]
readRegions . add ( new Box ( 277 , 165 , 572 , 40 ) ) ; //title[8]
readRegions . add ( new Box ( 716 , 502 , 226 , 45 ) ) ; //pct[9]
readRegions . add ( new Box ( 782 , 452 , 158 , 50 ) ) ; //maxcombo[10]
}
void seek ( int [ ] arr , int i , ColorRange SEEKCOLOR , Color FINALCOLOR , int width ) {
seek ( arr , i , SEEKCOLOR , FINALCOLOR , width , 0 ) ;
}
int seek ( int [ ] arr , int i , ColorRange SEEKCOLOR , Color FINALCOLOR , int width , int farthestRight ) {
arr [ i ] = FINALCOLOR . getRGB ( ) ;
int X = i % width ;
for ( int x = - 1 ; x < = 1 ; x + + ) {
for ( int y = - 1 ; y < = 1 ; y + + ) {
if ( SEEKCOLOR . colorInRange ( new Color ( arr [ i + x + y * width ] ) ) ) {
farthestRight = seek ( arr , i + x + y * width , SEEKCOLOR , FINALCOLOR , width , farthestRight ) ;
}
}
}
return X > farthestRight ? X : farthestRight ;
}
void ColorFilter ( int [ ] arr , int region , int width ) {
final int TRANSPARENT = new Color ( 0 , 0 , 0 , 0 ) . getRGB ( ) ;
switch ( region ) {
case 0 : {
final ColorRange TARGETCOLOR = new ColorRange ( 240 , 255 , 130 , 150 , 0 , 10 ) ;
final ColorRange SEEKINGCOLOR = new ColorRange ( 140 , 255 , 110 , 255 , 0 , 200 ) ;
final Color FINALCOLOR = Color . MAGENTA ;
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( TARGETCOLOR . colorInRange ( col ) ) {
seek ( arr , i , SEEKINGCOLOR , FINALCOLOR , width ) ;
}
}
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( ! col . equals ( Color . MAGENTA ) ) {
arr [ i ] = TRANSPARENT ;
}
}
} break ;
case 2 :
case 3 :
case 4 :
case 5 :
case 6 :
case 8 : {
final ColorRange TARGETCOLOR = new ColorRange ( 255 , 255 , 255 , 255 , 255 , 255 ) ;
final ColorRange SEEKINGCOLOR = new ColorRange ( 240 , 255 , 240 , 255 , 240 , 255 ) ;
final Color FINALCOLOR = Color . MAGENTA ;
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( TARGETCOLOR . colorInRange ( col ) ) {
seek ( arr , i , SEEKINGCOLOR , FINALCOLOR , width ) ;
}
}
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( ! col . equals ( Color . MAGENTA ) ) {
arr [ i ] = TRANSPARENT ;
}
}
} break ;
case 9 : {
final ColorRange TARGETCOLOR = new ColorRange ( 0 , 10 , 155 , 190 , 95 , 145 ) ;
final ColorRange SEEKINGCOLOR = new ColorRange ( 0 , 200 , 155 , 240 , 95 , 240 ) ;
final Color FINALCOLOR = Color . MAGENTA ;
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( TARGETCOLOR . colorInRange ( col ) ) {
seek ( arr , i , SEEKINGCOLOR , FINALCOLOR , width ) ;
}
}
final ColorRange SEEKINGCOLOR2 = new ColorRange ( 255 , 255 , 0 , 0 , 255 , 255 ) ;
final Color FINALCOLOR2 = new Color ( TRANSPARENT , true ) ;
for ( int x = 0 ; x < width ; x + + ) {
//30 pixels from top.
if ( arr [ 30 * width + x ] = = Color . MAGENTA . getRGB ( ) ) {
System . out . println ( "Start Jump: " + x ) ;
x = seek ( arr , 30 * width + x , SEEKINGCOLOR2 , FINALCOLOR2 , width , x ) ;
System . out . println ( "End Jump: " + x ) ;
}
}
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( ! col . equals ( Color . MAGENTA ) ) {
arr [ i ] = TRANSPARENT ;
}
}
} break ;
case 10 : {
final ColorRange TARGETCOLOR = new ColorRange ( 240 , 255 , 30 , 50 , 0 , 5 ) ;
final ColorRange SEEKINGCOLOR = new ColorRange ( 180 , 255 , 10 , 120 , 0 , 120 ) ;
final Color FINALCOLOR = Color . MAGENTA ;
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( TARGETCOLOR . colorInRange ( col ) ) {
seek ( arr , i , SEEKINGCOLOR , FINALCOLOR , width ) ;
}
}
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( ! col . equals ( Color . MAGENTA ) ) {
arr [ i ] = TRANSPARENT ;
}
}
} break ;
case 400 :
case 401 :
case 402 :
case 403 :
case 404 : {
final ColorRange TARGETCOLOR = new ColorRange ( 255 , 255 , 255 , 255 , 255 , 255 ) ;
final ColorRange SEEKINGCOLOR = new ColorRange ( 240 , 255 , 240 , 255 , 240 , 255 ) ;
final Color FINALCOLOR = Color . MAGENTA ;
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( TARGETCOLOR . colorInRange ( col ) ) {
seek ( arr , i , SEEKINGCOLOR , FINALCOLOR , width ) ;
}
}
for ( int i = 0 ; i < arr . length ; i + + ) {
Color col = new Color ( arr [ i ] , true ) ;
if ( ! col . equals ( Color . MAGENTA ) ) {
arr [ i ] = TRANSPARENT ;
}
}
} break ;
}
}
void interpretBoxes ( Path img ) {
/ * String dataString = readAllBoxes ( img ) ;
String [ ] data = dataString . split ( Pattern . quote ( "\n" ) ) ;
String [ ] ja_data = data [ 0 ] . split ( Pattern . quote ( ")" ) ) ;
String [ ] en_data = data [ 2 ] . split ( Pattern . quote ( ")" ) ) ;
trimAllData ( ja_data ) ;
trimAllData ( en_data ) ;
System . out . println ( Arrays . toString ( ja_data ) ) ;
System . out . println ( Arrays . toString ( en_data ) ) ; * /
int regionHeights = 0 ;
int maxWidth = 0 ;
for ( int i = 0 ; i < readRegions . size ( ) ; i + + ) {
regionHeights + = readRegions . get ( i ) . h + REGION_PADDING ;
if ( readRegions . get ( i ) . w > maxWidth ) {
maxWidth = readRegions . get ( i ) . w ;
}
}
try {
BufferedImage originalImg = ImageIO . read ( img . toFile ( ) ) ;
BufferedImage cutImg = new BufferedImage ( maxWidth , regionHeights , BufferedImage . TYPE_INT_ARGB ) ;
Graphics2D g = cutImg . createGraphics ( ) ;
int currentHeight = 0 ;
for ( int i = 0 ; i < readRegions . size ( ) ; i + + ) {
int [ ] arr = originalImg . getRGB ( readRegions . get ( i ) . x , readRegions . get ( i ) . y , readRegions . get ( i ) . w , readRegions . get ( i ) . h , null , 0 , readRegions . get ( i ) . w ) ;
//System.out.println(Arrays.toString(arr));
ColorFilter ( arr , i , readRegions . get ( i ) . w ) ;
//g.drawImage(originalImg, 0,currentHeight,readRegions.get(i).w,readRegions.get(i).h+currentHeight,readRegions.get(i).x, readRegions.get(i).y, readRegions.get(i).x+readRegions.get(i).w, readRegions.get(i).y+readRegions.get(i).h, null);
int leftMost = readRegions . get ( i ) . w ;
for ( int j = 0 ; j < arr . length ; j + + ) {
if ( arr [ j ] = = Color . MAGENTA . getRGB ( ) & & j % readRegions . get ( i ) . w < leftMost ) {
leftMost = j % readRegions . get ( i ) . w ;
}
}
if ( i > = 2 & & i < = 6 ) {
int [ ] arr2 = originalImg . getRGB ( extraRegions . get ( i - 2 ) . x , extraRegions . get ( i - 2 ) . y , extraRegions . get ( i - 2 ) . w , extraRegions . get ( i - 2 ) . h , null , 0 , extraRegions . get ( i - 2 ) . w ) ;
int rightMost = 0 ;
ColorFilter ( arr2 , 400 + i - 2 , extraRegions . get ( i - 2 ) . w ) ;
for ( int j = 0 ; j < arr2 . length ; j + + ) {
if ( arr2 [ j ] = = Color . MAGENTA . getRGB ( ) & & j % extraRegions . get ( i - 2 ) . w > rightMost ) {
rightMost = j % extraRegions . get ( i - 2 ) . w ;
}
}
//cutImg.setRGB(rightMost-leftMost,currentHeight,readRegions.get(i).w,readRegions.get(i).h,arr,0,readRegions.get(i).w);
cutImg . setRGB ( 0 , currentHeight , extraRegions . get ( i - 2 ) . w , extraRegions . get ( i - 2 ) . h , arr2 , 0 , extraRegions . get ( i - 2 ) . w ) ;
final int PADDING = 8 ;
for ( int x = leftMost ; x < readRegions . get ( i ) . w ; x + + ) {
for ( int y = 0 ; y < readRegions . get ( i ) . h ; y + + ) {
cutImg . setRGB ( x + rightMost - leftMost + PADDING , y + currentHeight , arr [ y * readRegions . get ( i ) . w + x ] ) ;
}
}
} else {
cutImg . setRGB ( 0 , currentHeight , readRegions . get ( i ) . w , readRegions . get ( i ) . h , arr , 0 , readRegions . get ( i ) . w ) ;
}
currentHeight + = readRegions . get ( i ) . h + REGION_PADDING ;
}
Path output = Paths . get ( "result.png" ) ;
ImageIO . write ( cutImg , "png" , output . toFile ( ) ) ;
String dataString = readAllBoxes ( output ) ;
String [ ] data = dataString . split ( Pattern . quote ( "\n" ) ) ;
String [ ] ja_data = data [ 0 ] . split ( Pattern . quote ( ")" ) ) ;
String [ ] en_data = data [ 2 ] . split ( Pattern . quote ( ")" ) ) ;
trimAllData ( ja_data ) ;
trimAllData ( en_data ) ;
System . out . println ( Arrays . toString ( ja_data ) ) ;
System . out . println ( Arrays . toString ( en_data ) ) ;
g . dispose ( ) ;
} catch ( IOException e ) {
e . printStackTrace ( ) ;
}
//System.out.println(data[0]);
//System.out.println(data[2]);
}
}
abstract class Reader {
int score ;
int rank ;
int [ ] notes = new int [ 7 ] ;
int difficulty ;
String title ;
int pct ;
int maxcombo ;
String other ;
List < Box > readRegions = new ArrayList < > ( ) ;
String readAllBoxes ( Path img ) {
try {
Process p = Runtime . getRuntime ( ) . exec ( new String [ ] { "python3" , "runocr.py" , "ja" , img . toAbsolutePath ( ) . toString ( ) } ) ;
while ( p . isAlive ( ) ) ;
InputStreamReader result = new InputStreamReader ( p . getInputStream ( ) ) ;
StringBuilder sb = new StringBuilder ( ) ;
while ( result . ready ( ) ) {
sb . append ( ( char ) result . read ( ) ) ;
}
result . close ( ) ;
sb . append ( "\n" ) ;
p = Runtime . getRuntime ( ) . exec ( new String [ ] { "python3" , "runocr.py" , "en" , img . toAbsolutePath ( ) . toString ( ) } ) ;
while ( p . isAlive ( ) ) ;
result = new InputStreamReader ( p . getInputStream ( ) ) ;
while ( result . ready ( ) ) {
sb . append ( ( char ) result . read ( ) ) ;
}
return sb . toString ( ) ;
} catch ( IOException e ) {
e . printStackTrace ( ) ;
}
return "" ;
}
void trimAllData ( String [ ] data ) {
StringBuilder sb = new StringBuilder ( ) ;
for ( int i = 0 ; i < data . length ; i + + ) {
sb . delete ( 0 , sb . length ( ) ) ;
for ( int j = 0 ; j < data [ i ] . length ( ) ; j + + ) {
if ( data [ i ] . charAt ( j ) ! = '[' & & data [ i ] . charAt ( j ) ! = '(' & & data [ i ] . charAt ( j ) ! = ')' & & data [ i ] . charAt ( j ) ! = ']' ) {
sb . append ( data [ i ] . charAt ( j ) ) ;
}
}
data [ i ] = sb . toString ( ) ;
}
}
}