Java ClassCastException in Code?

Hallo,
ich wollte den Code von dieser Seite
bufferedimage - Java - get pixel array from image - Stack Overflow
die 1. Antwort (die Methode ohne getRGB), in meinem Skript verwenden in etwas modifizierter Form, um einen Screnshot zu machen, den ich als BufferedImage dann verwende und eine bestimmte Reihe von Pixeln des Bildes untersuche. Den Screenshot erst als jpg zu speichern um ihn dann wieder einzulesen wäre ja eher witzlos.

Will im Endeffekt gucken ob sich in der Pixelreihe ein rotes, grünes oder weißes Pixel befindet oder eben keins der 3.

Hierzu habe ich den nachfolgenden Code geschrieben:

		
	public String ResultHelp() throws InterruptedException{
		
			 
				BufferedImage image = bot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
				
				final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
				final int width = image.getWidth();
				final int height = image.getHeight();
				final boolean hasAlphaChannel = image.getAlphaRaster() != null;
				int R=0;
				int G=0;
				int B=0;
				
				final int pixelLength = hasAlphaChannel?4:3;
				final int fac=pixelLength-3;
				//int x=1370;
				//int y=105;
			
			

				
				for(int x=1370, y=105; x<=1418;x++){
				
					
					int pixel=y*(width*pixelLength)+x*pixelLength;
					
					
					B= ((int) pixels[pixel + fac] & 0xff); // blue
					G= (((int) pixels[pixel + fac+1] & 0xff) << 8); // green
					R= (((int) pixels[pixel + fac+2] & 0xff) << 16); // red
					
					if(B>=80){
						return "S";
					}
					else if(R>=80){
						return "R";
					}
					else if(G<80){
						//Fall ohne Treffer
						continue;
					}
					else{
						return "Z";
					}
						
						
					
					
					//1370,105 bis 1418,105
					
					//RGB alle unter 30->nichts
					//R>80, bg<80->rot
					//rgb alle >80->schwarz aka B>80
					//g>80, br<80->Null
					
				}
				
				Thread.sleep(150);
				return "N";
		
		
		
	}

Keine Sorge, mein Script ist viel größer, hässlicher und unübersichtlicher.
Aber ich habe DIeselben klassen wie im kommentar auf gerwähnter Seite imported und der Robot , den ich bot genannt habe, ist ausserhalb der Methode auch definiert.

Nun bekomme ich aber beim Ausführen (wohlgemerkt nicht schon beim kompilieren!) diesen Fehler:

Exception in thread "main" java.lang.ClassCastException: class java.awt.image.DataBufferInt cannot be cast to class java.awt.image.DataBufferByte (java.awt.image.DataBufferInt and java.awt.image.DataBufferByte are in module java.desktop of loader 'bootstrap')
        at Test.ResultHelp(Test.java:522)
        at Test.GetResult(Test.java:588)
        at Test.DoUpdatesAfterRoll(Test.java:513)
        at Test.run(Test.java:119)
        at Test.<init>(Test.java:97)
        at Test.main(Test.java:86)

Ich habe nur wenig Ahnung vo dem, was da genau passiert mit den DatenBuffern und Co.

Ich habe auch, zum vergleich, mal den Originalcode aus dem kommentar benutzt, der wird komsicherweise ohne rigendeine Classcastexception ausgeführt.
Die Variable image sit doch vom Typ BufferedImage, wie im Originalcode auch, also warum kommt es heir dann zu dieser ClassCastexception?

Ich mache doch nix Anderes als, statt eine externe jpg Datei als Bufferedimage einzulesen, eben den robot einen Screenshot mahcen zu lassen, der als Bufferedimage gespeichert wird O_o

Ich danke jeden iM Voraus, denn ich blicke gerade gar nicht durch :frowning:

Der Fehler zur Laufzeit ist in der Codezeile danach (pixels =…). Nun ist das auf dem Handy etwas schwer zu erkennen, aber du kannst davon ausgehen, dass du in einen Typ casten willst, der es nicht ist…

Wenn du (nur) die RGB Werte brauchst, würde ich gar nicht mit dem Buffer hantieren, denn dafür gibt es bereits Methoden.

Die Bilddaten, die man z.B. mit einem Robot-Screenshot erstellt oder aus einer Datei liest, können in verschiedenen Formaten vorliegen. Es fängt schon damit an, ob das Bild einen Alpha-Kanal braucht oder nicht. Dann gibt’s noch Graustufenbilder. Und hunderte von Bild-Dateiformaten.

Ein BufferedImage ist eine ungeheuer mächtige Klasse. Die „versteckt“ unglaublich komplizierte interne Details, die man in den allermeisten Fällen nicht braucht.

Mit der Zeile

final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();

bohrst du ein tiefes Loch in diese Abstraktion. Du behauptest, du wüßtest GENAU wie die Daten intern gespeichert sind (nämlich als byte[] array). Aber … offenbar weißt du das nicht.

Wenn es nicht einen wirklich driftigen Grund gibt, warum die methode mit den getDataBuffer verwenden solltest, dann solltest du das nicht machen. (Und wenn du meinst, es gäbe einen, dann wüßte ich gerne, welcher das sein soll. Wenn es um „Performance“ geht: Nein, das ist hier kein sinnvoller Grund…)

Wenn es nur darum ginge, ~„die Exception zu verhindern“, dann könntest du sowas machen wie

    public String ResultHelp() throws InterruptedException{

        BufferedImage botImage = bot.createScreenCapture(...);
        BufferedImage image = convertToByteImage(botImage);
        ...

    }

    private static BufferedImage convertToByteImage(BufferedImage image)
    {
        return convertTo(image, BufferedImage.TYPE_3BYTE_BGR);
    }
    
    private static BufferedImage convertTo(BufferedImage image, int type)
    {
        BufferedImage newImage = new BufferedImage(
            image.getWidth(), image.getHeight(),
            type);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }    

Aber … der code, der danach kommt, ist nicht nur wegen des direkten DataBufferByte-Zugriffs ziemlich unschön. Schon dass du das pixelLength selbst reinrechnen musst, ist ja kagge.

Und … die Farbanalyse danach ergibt ja nicht mal ansatzweise Sinn… Zu dem Thema hatte ich mal das hier geschrieben:


Ein Beispiel, wie man das anwenden könnte:

ColorDetection

Hier der komplette Code dazu - Copy+Paste, Compilieren, Starten:

package bytewelt;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class BufferedImageBufferTest
{
    public static void main(String[] args) throws IOException
    {
        String url = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ad/HueScale.svg/320px-HueScale.svg.png";
        BufferedImage image = ImageIO.read(new URL(url));
        SwingUtilities.invokeLater(() -> createAndShowGui(image));
    }
    
    private static void createAndShowGui(BufferedImage image)
    {
        JPanel p = new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                super.paintComponent(g);
                g.drawImage(image, 0, 0, null);
            }
            @Override
            public Dimension getPreferredSize()
            {
                return new Dimension(image.getWidth(), image.getHeight());
            }
        };
        p.addMouseMotionListener(new MouseAdapter()
        {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                int x = e.getX();
                int y = e.getY();
                String colorString = getColorString(image, x, y);
                System.out.println("At " + x + " " + y + " color is " + colorString);
            }
        });
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);;
        f.getContentPane().add(p);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static String getColorString(BufferedImage image, int x, int y)
    {
        if (x < 0 || x >= image.getWidth())
        {
            return "none";
        }
        if (y < 0 || y >= image.getHeight())
        {
            return "none";
        }
        int rgb = image.getRGB(x, y);
        return getColorString(rgb);
    }
    
    private static String getColorString(int rgb)
    {
        float hsb[] = new float[3];
        int r = (rgb >> 16) & 0xFF;
        int g = (rgb >>  8) & 0xFF;
        int b = (rgb      ) & 0xFF;
        Color.RGBtoHSB(r, g, b, hsb);
        if      (hsb[1] < 0.1 && hsb[2] > 0.9) return "white";
        else if (hsb[2] < 0.1) return "black";
        else {
            float deg = hsb[0]*360;
            if      (deg >=   0 && deg <  30) return "red";
            else if (deg >=  30 && deg <  90) return "yellow";
            else if (deg >=  90 && deg < 150) return "green";
            else if (deg >= 150 && deg < 210) return "cyan";
            else if (deg >= 210 && deg < 270) return "blue";
            else if (deg >= 270 && deg < 330) return "magenta";
            else return "red";
        }        
    }
    
}
2 Likes

Vielleicht noch das Detail zur ClassCastException an sich:

Das BufferedImage, das mit dem Screenshot erstellt wird, speichert seine Daten nicht in einem DataBufferByte (als byte[] array), sondern in einem DataBufferInt (als int[] array). Das kann man bis zu einem gewissen Grad erkennen, wenn man mit getType den Typ des Bildes abfragt. Das könnte dann z.B. BufferedImage.TYPE_INT_ARGB sein. Aber wenn du Pech hast, ist es bei einem Bild, das aus einer Datei gelesen wird auch mal TYPE_CUSTOM, und dann bist du am Ar. Dann kannst du noch versuchen, direkt den Typ des DataBuffers abzufragen, und dich mit ColorSpace und ColorModel auseinandersetzen… aber wenn dieser gigantische Aufwand vermieden werden kann, indem du die einfache, leichte, bequeme, generische image.getRGB(...)-Methode verwendest, würde ich dir empfehlen, letzteres zu tun…

1 Like

Ich wollte eigentlich diese custom Version benutzen weil die Standard .getrgb() Methode langsam ist.

Bei dem Shcreiberling aus dem anderen Thread waren es 16 Sekunden bei der getrgb Methode vs 1.5 sekunden bei der custom methode.

Naja, ich will mindestens 3-4 mal das durchlaufen lassen. Und das soll nahc Möglichkeit vll. 1-2 Sekunden dauern, soll halt schnell sein, einfahc um schnell zu gucken welche Farbe sich dort befindet (mehrfach ausführen deshalb weil in dem Casino dämlicherweise die Zahl blinkt (2 sekunden da, dnan wieder 2 sekunden weg, so in etwa).

Jedenfalls ist mir dass es shcnell geht shcon wichtig.

An und für sich bräuchte ich auch keinen screenshot vom, naja, kompletten Bildschirm, ein wenige Pixel breites ganz bestimmtes Rechteck vom bildschirm würde mir reichen.
Aber als ich in der Vergangenheit da mit der robot Screenshot funktion rumspielte, hat er mir,s tatt wirklich nur ein jpg des bereichs zu machen, ein Bild gespeichert mit Größe des ganzen bildschirms und wo halt Alles ausser dem rechteckbereich schwarz war.
Also gleiche pixelgröße wie vorher, nur umgefärbt.
was ja witzlos ist, es sollte ja nur der 1 pixel x 30 pixel Rechteckbereich sein.

Der letzte kommentar macht da sich die Konsole da ja beschwert dass das eine ein DataBufferByte und das andere ein DataBufferInt sei.

Ob man vll. das eine ins Andere umwandeln kann oder so?

Der „Performance-Test“ aus der verlinkten Stackoverflow-Antwort ergibt wenig Sinn. Der Dateiname 12000X12000 deutet darauf hin, dass es dort um ein Bild mit etwa einhundertvierzig Millionen Pixeln geht. Und da dauert dann eine Rechnung 16 Sekunden. Bei deinen paar hundert Pixeln würde das ganze dann vielleicht drei oder vier Mikrosekunden dauern.

Der Test ergibt noch viel weniger Sinn, wenn man berücksichtigt, dass nicht klar ist, welchen BufferedImage.TYPE... die Bilder nun haben. Ja, offenbar irgendwas, was als byte[] gespeichert wird. Ansonsten würde der „schnelle“ Code, der dort gezeigt ist, genauso mit einer ClassCastException abkacheln, wie deiner.

A: „Ich kann schnell Kopfrechnen“
B: „Was ist 245 mal 328?“
A: „Das ergibt 1000“
B: „Das ist komplett falsch!“
A: „Ja, aber es war schnell

Ob man vll. das eine ins Andere umwandeln kann oder so?

Den code dafür hatte ich dir gezeigt. Mit der convertToByteImage-Funktion kannst du ein Bild so umwandeln, dass es mit dem DataBufferByte-Ansatz funktioniert. Aber … es sollte klar sein, dass das komplett sinnlos ist: Damit wird das komplette Bild umgewandelt, nur um nachher ein paar Pixel „so zu lesen, wie man will“ - obwohl es mit getRGB eine Methode gibt, die genau diese Umwandlung macht, wenn man sie braucht.

Eine Antwort, wo es um „Performance von BufferedImages“ (speziell: Skalierung) geht, aber wo ich angedeutet habe, wie schwierig es ist, da eine fundierte Aussage zu machen:

Eine Antwort, wo es auch um Performance geht, unter anderem die Frage, ob der Array-basierte Ansatz denn „besser“ ist, als getRGB:

Es ist kompliziert.


Bottom line: Verwende getRGB, und schreibe den Code so, dass er idiomatisch, allgemeingültig, verständlich und wartbar ist.

Und wenn irgendwas zu langsam ist, dann mach’ einen Benchmark (mit einem richtigen Profiler), und optimiere den entsprechenden Teil.

(In deinem Fall wird Robot#createScreenCapture das langsamste sein. Da hast du aber dann nicht viel Einfluß drauf. Dann kannst du natürlich anfangen, dir mit JNI deine eigene Lösung zu basteln…)

Was man auch probieren könnte, ist es den Typ von BufferedImage in ein Standardformat (wie z.B. BYTE_BGR) zu konvertieren bevor man mit getData() das Array ausliest. Das kostet zwar auch wieder Performance, könnte - wenn auf der GPU beschleunigt - aber immer noch mehr Performance als getRGB raushauen.

Und wenn der Bild-Typ bereits im entsprechenden Format ist, muss man natürlich nicht vorher umwandeln.

Das kann man machen indem man entweder die ColorConvertOp Klasse nimmt, oder in dem man das BufferedImage einfach in ein anderes BufferedImage mit dem entsprechenden Format zeichnet. Müsste man ausprobieren was schneller geht.

Ich hatte mit 1920x1080 (2073600 Bildpunkte) und mehreren getRGB()-Aufrufen noch nie Performanceprobleme. Warum nicht das hernehmen, was es schon gibt? Premature optimization is the root of all evil (Knuth).

Bikubische Interpolationen würden zum Beispiel verhältnismäßig lange dauern, aber einmal alle RGBs durchlaufen?

Im Moment versuche ich nun mal ganz primitiv mein Glück mit der Robot.getPixelColor(x,y) Methode, die bisher auch zu klappen scheint.
Wie shcnell die ist, kann ich npch nicht beurteilen da leider noch nciht Alles reibungslos läuft und ich schon am nächsten Problem festhänge:

(Siehe meine erste Antwort). Das würde funktionieren, aber … das mit dem „Standardformat“ ist so eine Sache. Meistens (subjektives sample…) sind Bilder in den INT...-Formaten gespeichert, und die Operationen darauf auch tendenziell am schnellsten. Und… nun kann man entweder 1900x1200 Pixel umwanden, oder die paar hundert, die man gerade braucht…

Achso ich hatte den Code nicht gelesen, nach „Wenn es nur darum geht die Exception zu vermeiden“ dachte ich castest du das halt zu einem int array und fertig, wie jeder vernünftige Programmierer das machen würde :smile:

Zumal der Rohdatenspeicherplatzverbrauch (langes Wort :smile:) eines int-Arrays auch geringer ist als der eines byte-Arrays. :smiley: Sprich, die Entropie der Rohdaten höher ist. Alles hat eben seine Ordnung. :wink:

Ähm.

byte bytes[] = new byte[123];
int ints[] = (int[])bytes;

kachelt genauso mit einer CCE ab. Aber das driftet jetzt schon etwas ab.

Rohdatenspeicherplatzverbrauch

Ist gleich, wenn es ein TYPE_4BYTE...-Typ ist.

Das ursprüngliche Problem war, das es sich um eine DataIntBuffer und nicht um einen DataByteBuffer handelt, aber versucht wurde zu DataByteBuffer zu casten :wink: