2011-12-30 6 views
6

Attualmente sto tentando di disegnare immagini sullo schermo ad una velocità normale come in un videogioco.Disegno di un'immagine utilizzando la precisione del livello sub-pixel utilizzando Graphics2D

Sfortunatamente, a causa della velocità con cui l'immagine si sta muovendo, alcuni fotogrammi sono identici perché l'immagine non ha ancora spostato un pixel intero.

C'è un modo per fornire i valori float a Graphics2D per la posizione sullo schermo per disegnare l'immagine, piuttosto che i valori int?

Inizialmente qui è quello che avevo fatto:

BufferedImage srcImage = sprite.getImage (); 
Position imagePosition = ... ; //Defined elsewhere 
g.drawImage (srcImage, (int) imagePosition.getX(), (int) imagePosition.getY()); 

Questa di soglie corso, in modo da l'immagine non si muove tra i pixel, ma salta da uno all'altro.

Il metodo successivo era impostare il colore della vernice su una texture e disegnare in una posizione specificata. Sfortunatamente, questo ha prodotto risultati errati che mostravano piastrellature piuttosto che correggere l'antialiasing.

g.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 

BufferedImage srcImage = sprite.getImage (); 

g.setPaint (new TexturePaint (srcImage, new Rectangle2D.Float (0, 0, srcImage.getWidth (), srcImage.getHeight ()))); 

AffineTransform xform = new AffineTransform (); 

xform.setToIdentity (); 
xform.translate (onScreenPos.getX (), onScreenPos.getY ()); 
g.transform (xform); 

g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight()); 

Cosa devo fare per ottenere l'effetto desiderato del rendering subpixel di un'immagine in Java?

risposta

6

È possibile utilizzare uno BufferedImage e AffineTransform, disegnare sull'immagine bufferizzata, quindi disegnare l'immagine bufferizzata sul componente nell'evento paint.

/* overrides the paint method */ 
    @Override 
    public void paint(Graphics g) { 
     /* clear scene buffer */ 
     g2d.clearRect(0, 0, (int)width, (int)height); 

     /* draw ball image to the memory image with transformed x/y double values */ 
     AffineTransform t = new AffineTransform(); 
     t.translate(ball.x, ball.y); // x/y set here, ball.x/y = double, ie: 10.33 
     t.scale(1, 1); // scale = 1 
     g2d.drawImage(image, t, null); 

     // draw the scene (double percision image) to the ui component 
     g.drawImage(scene, 0, 0, this); 
    } 

controllare il mio esempio completo qui: http://pastebin.com/hSAkYWqM

+0

Hey Lawren! Grazie per aver risposto. Grazie mille per avermi mostrato come farlo! –

+0

perché t.scale (1, 1); ?? AFAIK che l'istruzione non fa nulla? (scala alla sua dimensione effettiva?) – pakore

0

Modifica la risoluzione della tua immagine di conseguenza, non esiste una bitmap con coordinate sub-pixel, quindi in pratica quello che puoi fare è creare un'immagine in memoria più grande di quella che vuoi renderizzata sullo schermo, ma ti permette accuratezza "sub-pixel".

Quando si disegna in un'immagine più grande in memoria, si copia e si ricampiona nel render più piccolo visibile all'utente finale.

Per esempio: un'immagine di 100x100 ed è 50x50 ridimensionato/controparte resampled:

resampling

See: http://en.wikipedia.org/wiki/Resampling_%28bitmap%29

+0

Questo implementa anche un doppio buffer, quindi è possibile eseguire più revisioni dell'immagine in memoria mentre non si aggiorna l'interfaccia utente che può essere intensa per il processore – lawrencealan

+0

Immagine una palla che si muove attraverso lo schermo, ma si muove molto lentamente. Il suo spostamento di 1 pixel ogni secondo. Se disegna 2 volte allo schermo ogni secondo, poi la seconda volta, il disegno sarà esattamente lo stesso della prima volta. La terza volta mostrerà la palla che avrebbe spostato un pixel in un solo passaggio. Quello che voglio invece è che la palla venga spostata parzialmente in modo che possa essere disegnata come se fosse tra i due pixel. Questa funzionalità è disponibile in java? –

+0

Devi emularlo usando il processo di cui sopra. Quindi, diciamo che la tua area di rendering è 800x600, hai bisogno di un'immagine in memoria che è 2x - 1600x1200 - e invece di muoverti a livello di virgola mobile, ti sposti di pixel interi ... quindi invece di spostare una palla di 0,5 px lo spostate di 1 pixel nell'immagine con memoria più grande e quando lo ricampionate all'area visibile (800x600) verrà emulata la precisione sub-pixel. – lawrencealan

3

Puoi composito l'immagine da soli utilizzando precisione sub-pixel, ma è più lavoro da parte tua. La semplice interpolazione bilineare dovrebbe funzionare abbastanza bene per un gioco. Di seguito è riportato il codice psuedo-C++ per farlo.

Normalmente, per disegnare uno sprite in posizione (a, b) qualcosa, faresti in questo modo:

for (x = a; x < a + sprite.width; x++) 
{ 
    for (y = b; y < b + sprite.height; y++) 
    { 
     *dstPixel = alphaBlend (*dstPixel, *spritePixel); 
     dstPixel++; 
     spritePixel++; 
    } 
    dstPixel += destLineDiff; // Move to start of next destination line 
    spritePixel += spriteLineDiff; // Move to start of next sprite line 
} 

Per fare il rendering sub-pixel, si fa lo stesso ciclo, ma conto per la sub-pixel compensato in questo modo:

float xOffset = a - floor (a); 
float yOffset = b - floor (b); 
for (x = floor(a), spriteX = 0; x < floor(a) + sprite.width + 1; x++, spriteX++) 
{ 
    for (y = floor(b), spriteY = 0; y < floor (b) + sprite.height + 1; y++, spriteY++) 
    { 
     spriteInterp = bilinearInterp (sprite, spriteX + xOffset, spriteY + yOffset); 
     *dstPixel = alphaBlend (*dstPixel, spriteInterp); 
     dstPixel++; 
     spritePixel++; 
    } 
    dstPixel += destLineDiff; // Move to start of next destination line 
    spritePixel += spriteLineDiff; // Move to start of next sprite line 
} 

La funzione bilinearInterp() sarebbe simile a questa:

Pixel bilinearInterp (Sprite* sprite, float x, float y) 
{ 
    // Interpolate the upper row of pixels 
    Pixel* topPtr = sprite->dataPtr + ((floor (y) + 1) * sprite->rowBytes) + floor(x) * sizeof (Pixel); 
    Pixel* bottomPtr = sprite->dataPtr + (floor (y) * sprite->rowBytes) + floor (x) * sizeof (Pixel); 

    float xOffset = x - floor (x); 
    float yOffset = y - floor (y); 

    Pixel top = *topPtr + ((*(topPtr + 1) - *topPtr) * xOffset; 
    Pixel bottom = *bottomPtr + ((*(bottomPtr + 1) - *bottomPtr) * xOffset; 
    return bottom + (top - bottom) * yOffset; 
} 

Questo non dovrebbe usare memoria aggiuntiva, ma richiederà più tempo per il rendering.

+0

Come faccio a fare queste operazioni in java? Esistono ottimizzazioni che possono essere fatte? Ho molti oggetti e il calcolo di qualcosa del genere sembra che potrebbe richiedere del tempo ... –

+0

Non sono molto fluente in java, ma presumibilmente i tuoi folletti hanno una struttura, giusto? Nei linguaggi basati su C, di solito c'è un puntatore al primo pixel e un numero di byte per riga. Immagino in Java che probabilmente hai una matrice di pixel senza spazi tra le righe. Quindi uno sprite potrebbe letteralmente essere solo una vasta gamma di rosso, verde, blu, alfa (o forse alfa, rosso, verde, blu). Quindi, invece di calcolare l'indirizzo di una struttura di pixel, otterresti semplicemente gli offset di cui hai bisogno. In bilinearInterp, le prime 2 righe stanno solo calcolando l'indice dei 4 pixel attorno al punto desiderato. – user1118321

+0

Questo è corretto, ma calcolare per ogni frame nel mio codice potrebbe non essere il modo migliore. Speravo ci potesse essere una sorta di funzione built-in come parte dell'API Java che esegue questo. –

1

ho risolto con successo il mio problema dopo aver fatto una cosa del genere lawrencealan proposto.

Originariamente, avevo il codice seguente, dove g si trasforma in un 16: 9 sistema di coordinate prima del metodo è chiamato:

private void drawStar(Graphics2D g, Star s) { 

    double radius = s.getRadius(); 
    double x = s.getX() - radius; 
    double y = s.getY() - radius; 
    double width = radius*2; 
    double height = radius*2; 

    try { 

     BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png")); 
     g.drawImage(image, (int)x, (int)y, (int)width, (int)height, this); 

    } catch (IOException ex) { 
     Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); 
    } 

} 

Tuttavia, come osservato dalla interrogante Kaushik Shankar, ruotando i doppi posizioni in numeri interi fa "saltare" l'immagine, e trasformando le doppie dimensioni in numeri interi lo scala in scala "jumpy" (perché diavolo non si accetta il doppio ?!) g.drawImage. Quello che ho trovato a lavorare per me è stata la seguente:

private void drawStar(Graphics2D g, Star s) { 

    AffineTransform originalTransform = g.getTransform(); 

    double radius = s.getRadius(); 
    double x = s.getX() - radius; 
    double y = s.getY() - radius; 
    double width = radius*2; 
    double height = radius*2; 

    try { 

     BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png")); 

     g.translate(x, y); 
     g.scale(width/image.getWidth(), height/image.getHeight()); 

     g.drawImage(image, 0, 0, this); 

    } catch (IOException ex) { 
     Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); 
    } 

    g.setTransform(originalTransform); 

} 

Sembra un modo stupido di farlo però.

+0

Grazie per la pubblicazione; Ho finito per fare qualcosa di simile pure; è decisamente sfortunato che drawImage non abbia supporto per questo. –

+0

Incredibile grazie mille !! Inoltre, ho usato 'g.setRenderingHint (RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);' per renderlo ancora migliore. – Ajay

Problemi correlati