2011-12-30 16 views
6

Obecnie próbuję narysować obrazy na ekranie z normalną szybkością, jak w grze wideo.Rysowanie obrazu przy użyciu dokładności na poziomie subpikseli przy użyciu Graphics2D

Niestety, ze względu na szybkość, z jaką obraz się porusza, niektóre klatki są identyczne, ponieważ obraz nie przesunął jeszcze pełnego piksela.

Czy istnieje sposób dostarczenia wartości float do Graphics2D dla pozycji na ekranie w celu narysowania obrazu zamiast wartości int?

Początkowo oto co zrobiłem:

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

Ten progów Oczywiście, dzięki czemu obraz nie porusza się między pikselami, ale przeskakuje z jednego do następnego.

Następną metodą było ustawienie koloru farby na teksturę i rysowanie w określonej pozycji. Niestety, spowodowało to niepoprawne wyniki, które pokazały układanie płytek zamiast prawidłowego wygładzania krawędzi.

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()); 

Co należy zrobić, aby osiągnąć pożądany efekt renderowania subpikselowych obrazów w języku Java?

Odpowiedz

6

Możesz użyć BufferedImage i AffineTransform, narysować buforowany obraz, a następnie narysować buforowany obraz do komponentu w zdarzeniu 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); 
    } 

Sprawdź mój pełny przykład tutaj: http://pastebin.com/hSAkYWqM

+0

Hej Lawren! Dziękuję za odpowiedź. Dziękuję bardzo za pokazanie mi, jak to zrobić! –

+0

dlaczego t.scale (1, 1); ?? AFAIK, że instrukcja nic nie robi? (w skali do rzeczywistego rozmiaru?) – pakore

0

Zmień odpowiednio rozdzielczość obrazu, nie ma czegoś takiego jak mapa bitowa ze współrzędnymi pod pikselami, więc w zasadzie możesz zrobić obraz w pamięci większy niż to, co chcesz renderować na ekranie, ale pozwala Dokładność "subpiksela".

Podczas rysowania na większy obraz w pamięci, kopiujesz i próbkujesz go w mniejszym renderingu widocznym dla użytkownika końcowego.

Na przykład: 100x100 obraz i to 50x50 zmieniany/resampled odpowiednik:

resampling

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

+0

To również implementuje podwójny bufor, dzięki czemu można wykonać wiele poprawek do obrazu w pamięci, nie aktualizując interfejsu użytkownika, który może być intensywny procesor. – lawrencealan

+0

Obraz kulki poruszającej się po ekranie, ale poruszającej się bardzo wolno. Jego ruch 1 piksel na sekundę. Jeśli rysuję 2 razy na ekranie co sekundę, to za drugim razem rysunek będzie dokładnie taki sam jak za pierwszym razem. Za trzecim razem pokażemy piłkę, która przesunąłaby o jeden piksel w jednym kroku. Zamiast tego chciałbym, aby kula została częściowo przesunięta, aby można ją było narysować tak, jakby znajdowała się pomiędzy dwoma pikselami. Czy ta funkcja jest dostępna w języku Java? –

+0

Musisz emulować za pomocą powyższego procesu. Więc powiedz, że twój obszar renderowania ma rozmiar 800 x 600, potrzebujesz obrazu w pamięci, który jest dwa razy większy - 1600x1200 - i zamiast poruszać się na poziomie zmiennoprzecinkowym, poruszasz się po całym pikselach ... więc zamiast przesuwać piłkę 0.5 px przesuwasz go 1px w większym obrazie opartym na pamięci, a po ponownym dopasowaniu go do widocznego obszaru (800x600) będzie emulowana subpikselowa dokładność .. – lawrencealan

3

Można composite obraz samodzielnie za pomocą dokładność sub-pikseli, ale jest to bardziej praca z Twojej strony. Prosta interpolacja dwuliniowa powinna działać wystarczająco dobrze do gry. Poniżej znajduje się kod pseudo-C++ do wykonania tego.

Normalnie rysować ikonki na miejscu (a, b) coś, można zrobić tak:

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 
} 

zrobić rendering subpiksel, robisz tę samą pętlę, ale stanowią dla subpiksel przesunięte tak:

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 
} 

funkcja bilinearInterp() będzie wyglądać mniej więcej tak:

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; 
} 

Nie należy używać dodatkowej pamięci, ale jej renderowanie zajmie więcej czasu.

+0

Jak miałbym wykonywać te operacje w Javie? Czy można dokonać optymalizacji? Mam wiele obiektów i obliczenie czegoś takiego wydaje się jak to może trochę potrwać ... –

+0

Nie jestem zbyt biegły w Javie, ale przypuszczalnie twoje duszki mają jakąś strukturę, prawda? W językach opartych na języku C zwykle istnieje wskaźnik do pierwszego piksela i liczba bajtów na wiersz. W Javie wyobrażam sobie, że prawdopodobnie macie tablicę pikseli bez przerw między wierszami. Zatem sprite może być dosłownie bardzo szeroką gamą czerwieni, zieleni, błękitu, alfa (lub ewentualnie alfy, czerwieni, zieleni, błękitu). Zamiast więc obliczać adres struktury pikseli, wystarczy uzyskać potrzebne korekcje. W bilinearInterp pierwsze 2 linie obliczają wskaźnik 4 pikseli wokół pożądanego punktu. – user1118321

+0

To prawda, ale obliczenie dla każdej ramki w moim kodzie może nie być najlepszym sposobem. Miałem nadzieję, że może istnieć jakaś wbudowana funkcja jako część Java API, która to wykonuje. –

1

pomyślnie rozwiązać mój problem po zrobieniu czegoś podobnego lawrencealan proponowanej.

Początkowo miałem następujący kod, gdzie g przekształca się w formacie 16: 9 układ współrzędnych przed metoda nazywa się:

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); 
    } 

} 

Jednakże, jak zauważył pytającego Kaushik Shankar, obracając podwójnych stanowisk na liczby całkowite sprawia, że ​​obraz "skacze" dookoła, a przekształcanie podwójnych wymiarów w liczby całkowite powoduje, że skaluje się "skocznie" (dlaczego, do licha, g.drawImage nie przyjmuje podwojeń ?!). To, co dla mnie znalazłem, było następujące:

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); 

} 

Wydaje się jednak, że to głupi sposób.

+0

Dzięki za wysyłkę; W końcu zrobiłem coś podobnego; jest zdecydowanie niefortunne, że drawImage nie ma na to żadnego wsparcia. –

+0

Niesamowite dzięki bardzo! Ponadto użyłem 'g.setRenderingHint (RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);', aby wyglądało jeszcze lepiej. – Ajay

Powiązane problemy