Hlavní navigace

Pohled pod kapotu JVM – bitmapy typu BufferedImage a VolatileImage v celoobrazovkových režimech

17. 12. 2013
Doba čtení: 28 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy si na čtveřici demonstračních příkladů ukážeme, jakým způsobem je možné využít bitmapy (rastrové obrázky) typu BufferedImage a VolatileImage v celoobrazovkových (exkluzivních) grafických režimech, a to včetně podpory průhlednosti.

Obsah

1. Pohled pod kapotu JVM – bitmapy typu BufferedImageVolatileImage v celoobrazovkových režimech

2. První demonstrační příklad: vykreslení pozadí a spritu v celoobrazovkovém režimu

3. Zamezení poblikávání s využitím bitmapy typu BufferedImage ve funkci offscreen bufferu

4. Druhý demonstrační příklad: vykreslení pozadí a spritu do bufferu tvořeného bitmapou typu BufferedImage

5. Využití bitmap typu VolatileImage

6. Detekce změny stavu grafické konfigurace a zneplatnění obsahu volatilních bitmap

7. Třetí demonstrační příklad: vykreslení pozadí a spritu do bufferu tvořeného bitmapou typu VolatileImage

8. Problematika průhledných obrázků

9. Čtvrtý demonstrační příklad: vykreslení pozadí a poloprůhledného spritu do bufferu tvořeného bitmapou typu VolatileImage

10. Repositář se zdrojovými soubory dnešních demonstračních příkladů

11. Odkazy na Internetu

1. Pohled pod kapotu JVM – bitmapy typu BufferedImageVolatileImage v celoobrazovkových režimech

V předchozí části tohoto seriálu jsme si vysvětlili a na demonstračním příkladu (benchmarku) ukázali, jakým způsobem současné virtuální stroje jazyka Java vykreslují rastrové obrázky (bitmapy) typu BufferedImage popř. VolatileImage. Taktéž jsme si řekli základní rozdíly mezi těmito dvěma typy bitmap. Dnes se budeme zabývat poněkud praktičtější problematikou; konkrétně možnostmi použití bitmap BufferedImageVolatileImage při práci ve vybraném celoobrazovkovém exkluzivním grafickém režimu (fullscreen exclusive graphics mode). Připomeňme si, že JVM umožňuje na většině systémů nastavení celoobrazovkového grafického režimu se zvoleným rozlišením (například 800×600 pixelů či 1600×1200 pixelů), zvolenou bitovou hloubkou (většinou 8bpp, 16bpp, 24bpp a 32bpp) a taktéž vybranou obnovovací frekvencí obrazu. Následně je možné provádět „exkluzivní“ vykreslování přes předem nastavené a maximalizované okno (popř. rámec, dialog či applet), které má k dispozici celou plochu obrazovky.

Slovem „exkluzivní“ je v předchozím odstavci myšlen fakt, že okna a dialogy ostatních aplikací nebudou viditelná; při zapnutí celoobrazovkového režimu se tak vlastně vracíme do doby před rozšířením správců oken (což ostatně není při pohledu na směr, kterým se vyvíjí moderní desktop, až tak špatná myšlenka :-). Nabídka grafických režimů je závislá jak na vlastnostech grafické karty či grafického akcelerátoru, tak i na konfiguraci monitorů. My dnes budeme pro jednoduchost předpokládat, že je ke grafické kartě/akcelerátoru připojen pouze jediný monitor, popř. že všechny monitory budou zobrazovat stejný snímek. JVM sice umožňuje práci i na systémech s více monitory (a dokonce i větším množstvím grafických karet), to je však mnohdy poměrně komplikovaná problematika, kterou se budeme zabývat v samostatném článku (navíc mnoho aplikací nepracuje při použití více monitorů korektně, například nerespektuje nastavení primárního displeje, zobrazuje dialogy centrované uprostřed mezi monitory atd. atd.).

2. První demonstrační příklad: vykreslení pozadí a spritu v celoobrazovkovém režimu

V dnešním prvním demonstračním příkladu je ukázáno vykreslování bitmap v celoobrazovkovém režimu. Tento příklad po svém spuštění nejprve inicializuje rámec (JFrame), u nějž jsou vypnuty okraje a titulkový pruh. Následně je pro tento rámec povolen celoobrazovkový režim:

    /**
     * Nastaveni parametru okna a zobrazeni.
     * @param gd 
     */
    private void showJFrame(GraphicsDevice gd) {
        // vypneme okraje a dalsi zbytecnosti :)
        this.setUndecorated(true);
        this.setLocation(0, 0);
        this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.createBufferStrategy(1);
        this.setVisible(true);
        gd.setFullScreenWindow(this);
    }

Posléze je nastaven celoobrazovkový grafický režim s rozlišením 1024×768 pixelů a bitovou hloubkou 16bpp. Tuto část programu si můžete sami upravit na základě vlastností konkrétního počítače, ostatní části programu se novému rozlišení dokážou přizpůsobit (pozor: na některých systémech není možné specifikovat barvovou hloubku; můžete zde však zadat hodnotu –1 a JVM samo vybere vhodný grafický režim):

    /**
     * Nastaveni grafickeho rezimu.
     */
    private void setDisplayMode(GraphicsDevice gd) {
        DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN);
        gd.setDisplayMode(dm);
    }

Při vykreslování se používá dvojice bitmap nazvaná pozadí (background) a sprite. Pozadí je neměnné a rozměry této bitmapy jsou stejné jako rozlišení nastaveného grafického režimu. Sprite se naproti tomu neustále pohybuje nad pozadím. Vlastní vykreslovací rutina využívá metodu Graphics.drawImage(), která je na většině současných počítačů akcelerovaná (pokud tedy nedojde k problémům s nezarovnanými řádky, což jsme si vysvětlili minule):

    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
        // vykreslime pozadi
        g.drawImage(background, 0, 0, null);
        // nad nej vykreslime pohybujici se sprite
        g.drawImage(sprite, spriteX, spriteY, null);
        // neni zcela nutne
        g.dispose();
        this.repaintCount++;
    }

Následuje výpis celého zdrojového kódu demonstračního příkladu BlitTest2:

import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
 
import javax.swing.JFrame;
import javax.swing.WindowConstants;
 
/**
 * Jednoduchy test metody Graphics.drawImage().
 * Pri vykreslovani se nebude pouzivat double buffering.
 */
public class BlitTest2 extends JFrame {
 
    /**
     * Generated serial version UID.
     */
    private static final long serialVersionUID = -6455857608125974217L;
 
    /**
     * Sirka testovaciho obrazku.
     */
    private static final int IMAGE_WIDTH = 256;
 
    /**
     * Vyska testovaciho obrazku.
     */
    private static final int IMAGE_HEIGHT = 256;
 
    /**
     * Sirka okna ve fullscreen rezimu.
     */
    private static final int FRAME_WIDTH = 1024;
 
    /**
     * Vyska okna ve fullscreen rezimu.
     */
    private static final int FRAME_HEIGHT = 768;
 
    /**
     * Bitova hloubka nastavovaneho grafickeho rezimu.
     */
    private static final int BPP = 16;
 
    /**
     * Horizontalni souradnice obrazku.
     */
    private int imageX;
 
    /**
     * Vertikalni souradnice obrazku.
     */
    private int imageY;
 
    /**
     * Pocet skutecnych volani metody paint()
     */
    private int repaintCount = 0;
 
    /**
     * Obrazek, ktery se bude prenaset do okna.
     */
    private BufferedImage spriteImage = null;
 
    /**
     * Obrazek zobrazeny na pozadi okna.
     */
    private BufferedImage backgroundImage = null;
 
    /**
     * Spusteni testu.
     */
    private void run() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
 
        showJFrame(gd);
        setDisplayMode(gd);
        createSpriteImage();
        createBackgroundImage();
        runBenchmark();
 
        gd.setFullScreenWindow(null);
        this.dispose();
        System.out.println("Repaint count: " + this.repaintCount);
        System.exit(0);
    }
 
    /**
     * Nastaveni grafickeho rezimu.
     */
    private void setDisplayMode(GraphicsDevice gd) {
        DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN);
        gd.setDisplayMode(dm);
    }
 
    /**
     * Spusteni vlastniho benchmarku.
     */
    private void runBenchmark() {
        for (int y = 0; y < FRAME_HEIGHT; y += 10) {
            for (int x = 0; x < FRAME_WIDTH; x += 10) {
                setImageX(x);
                setImageY(y);
                // vynutime si prekresleni okna
                repaint();
                try {
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * Vytvoreni testovaciho obrazku.
     */
    private void createSpriteImage() {
        this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT);
        drawIntoSpriteImage(this.spriteImage);
    }
 
    /**
     * Vytvoreni obrazku pozadi.
     */
    private void createBackgroundImage() {
        this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT);
        drawIntoBackgroundImage(this.backgroundImage);
    }
 
    /**
     * Vykresleni vzorku do obrazku.
     */
    private void drawIntoSpriteImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni cerveneho ctverce
        g.setColor(Color.red);
        g.drawRect(0, 0, width-1, height-1);
 
        // vykresleni krize
        g.setColor(Color.blue);
        g.drawLine(width >> 1, height/3, width >> 1, 2*height/3);
        g.drawLine(width/3, height >> 1, 2*width/3, height >> 1);
        g.dispose();
    }
 
    /**
     * Vykresleni vzorku do obrazku na pozadi.
     */
    private void drawIntoBackgroundImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni zelene sachovnice
        g.setColor(Color.green);
        for (int y = 0; y < height; y += 20) {
            g.drawLine(0, y, width - 1, y);
        }
        g.setColor(Color.green);
        for (int x = 0; x < width; x += 20) {
            g.drawLine(x, 0, x, height - 1);
        }
 
        g.dispose();
    }
 
    /**
     * Nastaveni parametru okna a zobrazeni.
     * @param gd 
     */
    private void showJFrame(GraphicsDevice gd) {
        // vypneme okraje a dalsi zbytecnosti :)
        this.setUndecorated(true);
        this.setLocation(0, 0);
        this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.createBufferStrategy(1);
        this.setVisible(true);
        gd.setFullScreenWindow(this);
    }
 
    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
        // vykreslime pozadi
        g.drawImage(background, 0, 0, null);
        // nad nej vykreslime pohybujici se sprite
        g.drawImage(sprite, spriteX, spriteY, null);
        // neni zcela nutne
        g.dispose();
        this.repaintCount++;
    }
 
    /**
     * Nastaveni horizontalni souradnice obrazku.
     *
     * @param x
     */
    void setImageX(int x) {
        this.imageX = x;
    }
 
    /**
     * Nastaveni vertikalni souradnice obrazku.
     *
     * @param y
     */
    void setImageY(int y) {
        this.imageY = y;
    }
 
    /**
     * Getter pro atribut imageX.
     */
    int getImageX() {
        return this.imageX;
    }
 
    /**
     * Getter pro atribut imageY.
     */
    int getImageY() {
        return this.imageY;
    }
 
    /**
     * Getter pro atribut spriteImage.
     */
    BufferedImage getSpriteImage() {
        return this.spriteImage;
    }
 
    /**
     * Getter pro atribut backgroundImage.
     */
    BufferedImage getBackgroundImage() {
        return this.backgroundImage;
    }
 
    /**
     * Spusteni testu.
     * @param args
     */
    public static void main(String[] args) {
        new BlitTest2().run();
    }
 
}

3. Zamezení poblikávání s využitím bitmapy typu BufferedImage ve funkci offscreen bufferu

Pokud jste si dnešní první demonstrační příklad spustili, pravděpodobně jste zaznamenali nepříjemné blikání celé vykreslované scény. To je způsobeno tím, že se bitmapa pozadí a bitmapa spritu vykresluje přímo na obrazovku, takže uživatel vidí nejdříve vykreslení pozadí (a tím pádem i přemazání spritu z předchozího snímku) s následným vykreslením spritu na novou pozici. Toto chování je samozřejmě pro většinu aplikací neakceptovatelné, takže je nutné zvolit jiné řešení. V průběhu přibližně čtyřiceti let vývoje grafických subsystémů se ukázalo, že vhodné a současně i dostatečně obecné řešení tohoto problému spočívá v použití takzvaného offscreen bufferu, tj. bitmapy (rastrového obrázku), do níž se postupně vykreslují jednotlivé grafické objekty a na závěr – když je již vše řádně vykresleno, je offscreen buffer buď přenesen na obrazovku, nebo je jednoduše prohozen (operace typu flip) s bufferem, který je v daném okamžiku viditelný na obrazovce (double buffering).

My si dnes představíme první možnost – vykreslování do offscreen bufferu s následným blokovým vykreslením již připraveného bufferu na obrazovku (double buffering bude popsán příště). Kromě obou již připravených bitmap (pozadí a spritu) vytvoříme ještě další bitmapu, jejíž rozměry budou stejné jako rozlišení nastaveného grafického režimu. Bitmapa bude typu BufferedImage a vytvoříme ji voláním metody GraphicsConfiguration.cre­ateCompatibleImage(). Vytvoření této bitmapy může probíhat až v metodě paint(), a to z toho důvodu, že zde máme zaručeno, že se vytvoří bitmapa skutečně kompatibilní (se stejným formátem pixelů) s nastaveným grafickým režimem:

    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
 
        // konstrukce offscreen bufferu, pokud jeste nebyl vytvoren
        if (this.offscreen == null) {
            GraphicsConfiguration gc = ((Graphics2D)g).getDeviceConfiguration();
            this.offscreen = gc.createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT);
        }
 
        // vykreslime pozadi
        this.offscreen.getGraphics().drawImage(background, 0, 0, null);
        // nad nej vykreslime pohybujici se sprite
        this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null);
        // ted jiz konecne vykreslujeme na obrazovku
        g.drawImage(this.offscreen, 0, 0, null);
        g.dispose();
        this.repaintCount++;
    }

4. Druhý demonstrační příklad: vykreslení pozadí a spritu do bufferu tvořeného bitmapou typu BufferedImage

Způsob využití bitmapy typu BufferedImage ve funkci offscreen bufferu je ukázán v dnešním druhém demonstračním příkladu nazvaném BlitTest3, jehož zdrojový kód je zobrazen pod tímto odstavcem:

import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
 
import javax.swing.JFrame;
import javax.swing.WindowConstants;
 
/**
 * Jednoduchy test metody Graphics.drawImage().
 * Pri vykreslovani se nebude pouzivat double buffering.
 * Predbezne vykreslovani se bude provadet do nevolatilniho obrazku.
 */
public class BlitTest3 extends JFrame {
 
    /**
     * Generated serial version UID.
     */
    private static final long serialVersionUID = -6455857608125974217L;
 
    /**
     * Sirka testovaciho obrazku.
     */
    private static final int IMAGE_WIDTH = 256;
 
    /**
     * Vyska testovaciho obrazku.
     */
    private static final int IMAGE_HEIGHT = 256;
 
    /**
     * Sirka okna ve fullscreen rezimu.
     */
    private static final int FRAME_WIDTH = 1024;
 
    /**
     * Vyska okna ve fullscreen rezimu.
     */
    private static final int FRAME_HEIGHT = 768;
 
    /**
     * Bitova hloubka nastavovaneho grafickeho rezimu.
     */
    private static final int BPP = 16;
 
    /**
     * Horizontalni souradnice obrazku.
     */
    private int imageX;
 
    /**
     * Vertikalni souradnice obrazku.
     */
    private int imageY;
 
    /**
     * Pocet skutecnych volani metody paint()
     */
    private int repaintCount = 0;
 
    /**
     * Obrazek, ktery se bude prenaset do okna.
     */
    private BufferedImage spriteImage = null;
 
    /**
     * Obrazek zobrazeny na pozadi okna.
     */
    private BufferedImage backgroundImage = null;
 
    /**
     * Obrazek pouzity pro kresleni mimo obrazovku.
     */
    private Image offscreen;
 
    /**
     * Spusteni testu.
     */
    private void run() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
 
        showJFrame(gd);
        setDisplayMode(gd);
        createSpriteImage();
        createBackgroundImage();
        runBenchmark();
 
        gd.setFullScreenWindow(null);
        this.dispose();
        System.out.println("Repaint count: " + this.repaintCount);
        System.exit(0);
    }
 
    /**
     * Nastaveni grafickeho rezimu.
     */
    private void setDisplayMode(GraphicsDevice gd) {
        DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN);
        gd.setDisplayMode(dm);
    }
 
    /**
     * Spusteni vlastniho benchmarku.
     */
    private void runBenchmark() {
        for (int y = 0; y < FRAME_HEIGHT; y += 10) {
            for (int x = 0; x < FRAME_WIDTH; x += 10) {
                setImageX(x);
                setImageY(y);
                // vynutime si prekresleni okna
                repaint();
                try {
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * Vytvoreni testovaciho obrazku.
     */
    private void createSpriteImage() {
        this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT);
        drawIntoSpriteImage(this.spriteImage);
    }
 
    /**
     * Vytvoreni obrazku pozadi.
     */
    private void createBackgroundImage() {
        this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT);
        drawIntoBackgroundImage(this.backgroundImage);
    }
 
    /**
     * Vykresleni vzorku do obrazku.
     */
    private void drawIntoSpriteImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni cerveneho ctverce
        g.setColor(Color.red);
        g.drawRect(0, 0, width-1, height-1);
 
        // vykresleni krize
        g.setColor(Color.blue);
        g.drawLine(width >> 1, height/3, width >> 1, 2*height/3);
        g.drawLine(width/3, height >> 1, 2*width/3, height >> 1);
        g.dispose();
    }
 
    /**
     * Vykresleni vzorku do obrazku na pozadi.
     */
    private void drawIntoBackgroundImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni zelene sachovnice
        g.setColor(Color.green);
        for (int y = 0; y < height; y += 20) {
            g.drawLine(0, y, width - 1, y);
        }
        g.setColor(Color.green);
        for (int x = 0; x < width; x += 20) {
            g.drawLine(x, 0, x, height - 1);
        }
 
        g.dispose();
    }
 
    /**
     * Nastaveni parametru okna a zobrazeni.
     * @param gd 
     */
    private void showJFrame(GraphicsDevice gd) {
        // vypneme okraje a dalsi zbytecnosti :)
        this.setUndecorated(true);
        this.setLocation(0, 0);
        this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.createBufferStrategy(1);
        this.setVisible(true);
        gd.setFullScreenWindow(this);
    }
 
    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
 
        if (this.offscreen == null) {
            GraphicsConfiguration gc = ((Graphics2D)g).getDeviceConfiguration();
            this.offscreen = gc.createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT);
        }
 
        // vykreslime pozadi
        this.offscreen.getGraphics().drawImage(background, 0, 0, null);
        // nad nej vykreslime pohybujici se sprite
        this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null);
        g.drawImage(this.offscreen, 0, 0, null);
        g.dispose();
        this.repaintCount++;
    }
 
    /**
     * Nastaveni horizontalni souradnice obrazku.
     *
     * @param x
     */
    void setImageX(int x) {
        this.imageX = x;
    }
 
    /**
     * Nastaveni vertikalni souradnice obrazku.
     *
     * @param y
     */
    void setImageY(int y) {
        this.imageY = y;
    }
 
    /**
     * Getter pro atribut imageX.
     */
    int getImageX() {
        return this.imageX;
    }
 
    /**
     * Getter pro atribut imageY.
     */
    int getImageY() {
        return this.imageY;
    }
 
    /**
     * Getter pro atribut spriteImage.
     */
    BufferedImage getSpriteImage() {
        return this.spriteImage;
    }
 
    /**
     * Getter pro atribut backgroundImage.
     */
    BufferedImage getBackgroundImage() {
        return this.backgroundImage;
    }
 
    /**
     * Spusteni testu.
     * @param args
     */
    public static void main(String[] args) {
        new BlitTest3().run();
    }
 
}

5. Využití bitmap typu VolatileImage

V předchozím demonstračním příkladu byla pro implementaci offscreen bufferu použita bitmapa typu BufferedImage. Ovšem když si uvědomíme, jakým způsobem je tento buffer použit, můžeme dojít k závěru, že výhodnější by bylo použití bitmapy typu VolatileImage, a to především z toho důvodu, že buffer používáme pouze v jediné metodě paint() a navíc nám nebude vadit, pokud se v intervalu mezi voláním této metody obsah volatilní bitmapy ztratí (což se může stát, ostatně právě z této vlastnosti se odvozuje název tohoto typu rastrových obrázků). Úprava demonstračního příkladu tak, aby pro implementaci offscreen bufferu používal volatilní rastrové obrazy, je však poněkud komplikovanější, neboť je nutné správně reagovat na změnu stavu grafické konfigurace i na zneplatnění obsahu volatilní bitmapy (zneplatněním je zde myšlen přepis hodnot pixelů jinými údaji).

6. Detekce změny stavu grafické konfigurace a zneplatnění obsahu volatilních bitmap

Již v předchozí části tohoto seriálu jsme si řekli, že nezávisle na použití či nepoužití exkluzivního celoobrazovkového režimu mohou nastat situace, kdy dojde k poškození obrazových dat (a v důsledku toho i volatilních bitmap). Může se například jednat o zobrazení zprávy z task manageru (ten má vysokou prioritu), spuštění screensaveru, přechodu systému z/do režimu spánku či režimu uspání, spuštění či přepnutí do další aplikace vyžadující celoobrazovkový režim atd. Z tohoto důvodu JVM sleduje stav volatilních bitmap a ukládá tento stav do atributu dostupného přes metodu validate() (ta má ještě další význam). Volatilní bitmapa se může nacházet v jednom ze tří stavů:

  1. IMAGE_OK: validní bitmapa, obsah se neztratil.
  2. IMAGE_INCOMPATIBLE: pravděpodobně se změnil grafický režim (i ukončení celoobrazovkového režimu).
  3. IMAGE_RESTORED: bitmapu je nutno překreslit.

Kromě volání metody validate() je ještě nutné volat metodu contentsLost(), která vrací pravdivostní hodnotu true/false podle toho, zda došlo z nějakého důvodu k poškození obrazových dat (pixelů) uložených do volatilního obrázku.

Ukažme si nyní, jak by mohla vypadat kostra metody paint() naprogramované takovým způsobem, aby se volatilní bitmapa správně použila a/nebo obnovila jak ve chvíli, kdy se změní grafický režim (resp. formát ukládaných pixelů), tak i ve chvíli, kdy se obsah bitmapy přemaže jinými daty:

    @Override
    public void paint(Graphics g) {
        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();
        do {
            int valid = offscreen.validate(graphicsConfiguration);
 
            if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
                // zmena grafickeho rezimu?
                offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
 
            // vykresleni cehokoli do volatilni bitmapy "offscreen"
            ...
            ...
            ...
            // prenos volatilni bitmapy na obrazovku
            g.drawImage(offscreen, 0, 0, null);
            g.dispose();
        } while (offscreen.contentsLost());
    }

Výše uvedený kód skutečně řeší obě situace (a to navíc s použitím málo využívané programové smyčky typu do-while): pokud se změní grafický režim, dojde k vytvoření nové „kompatibilní“ bitmapy, pokud se v průběhu vykreslování obsah volatilní bitmapy přepíše jinými daty, bude se celá smyčka znovu opakovat. Zde ve skutečnosti může dojít k vizuálním chybám, reagovat na přepis hodnoty pixelů lze pro jistotu ještě před zavoláním metody g.drawImage() (přidáním nové podmínky, schválně ponechávám úpravu na váženém čtenáři).

Podívejme se nyní, jak lze upravit metodu paint() z předchozího demonstračního příkladu tak, aby se pro offscreen buffer používala bitmapa typu VolatileImage namísto BufferedImage:

    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
 
        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();
 
        do {
            if (this.offscreen == null) {
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
            int valid = this.offscreen.validate(graphicsConfiguration);
 
            if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
                // zmena grafickeho rezimu?
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
 
            // vykresleni pozadi
            this.offscreen.getGraphics().drawImage(background, 0, 0, null);
            // vykresleni spritu
            this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null);
            // prenos na obrazovku
            g.drawImage(this.offscreen, 0, 0, null);
            g.dispose();
        } while (this.offscreen.contentsLost());
        this.repaintCount ++;
    }

7. Třetí demonstrační příklad: vykreslení pozadí a spritu do bufferu tvořeného bitmapou typu VolatileImage

Následuje výpis zdrojového kódu dnešního třetího demonstračního příkladu, v němž se již používají volatilní bitmapy. Zda skutečně dojde ke snížení zátěže mikroprocesoru a/nebo grafického akcelerátoru, lze opět zjistit s využitím profileru:

import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
 
import javax.swing.JFrame;
import javax.swing.WindowConstants;
 
/**
 * Jednoduchy test metody Graphics.drawImage().
 * Pri vykreslovani se nebude pouzivat double buffering.
 * Predbezne vykreslovani se bude provadet do volatilniho obrazku.
 */
public class BlitTest4 extends JFrame {
 
    /**
     * Generated serial version UID.
     */
    private static final long serialVersionUID = -6455857608125974217L;
 
    /**
     * Sirka testovaciho obrazku.
     */
    private static final int IMAGE_WIDTH = 256;
 
    /**
     * Vyska testovaciho obrazku.
     */
    private static final int IMAGE_HEIGHT = 256;
 
    /**
     * Sirka okna ve fullscreen rezimu.
     */
    private static final int FRAME_WIDTH = 1024;
 
    /**
     * Vyska okna ve fullscreen rezimu.
     */
    private static final int FRAME_HEIGHT = 768;
 
    /**
     * Bitova hloubka nastavovaneho grafickeho rezimu.
     */
    private static final int BPP = 16;
 
    /**
     * Horizontalni souradnice obrazku.
     */
    private int imageX;
 
    /**
     * Vertikalni souradnice obrazku.
     */
    private int imageY;
 
    /**
     * Pocet skutecnych volani metody paint()
     */
    private int repaintCount = 0;
 
    /**
     * Obrazek, ktery se bude prenaset do okna.
     */
    private BufferedImage spriteImage = null;
 
    /**
     * Obrazek zobrazeny na pozadi okna.
     */
    private BufferedImage backgroundImage = null;
 
    /**
     * Volatilni obrazek pouzity pro kresleni mimo obrazovku.
     */
    private VolatileImage offscreen = null;
 
    /**
     * Spusteni testu.
     */
    private void run() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
 
        showJFrame(gd);
        setDisplayMode(gd);
        createSpriteImage();
        createBackgroundImage();
        runBenchmark();
 
        gd.setFullScreenWindow(null);
        this.dispose();
        System.out.println("Repaint count: " + this.repaintCount);
        System.exit(0);
    }
 
    /**
     * Nastaveni grafickeho rezimu.
     */
    private void setDisplayMode(GraphicsDevice gd) {
        DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN);
        gd.setDisplayMode(dm);
    }
 
    /**
     * Spusteni vlastniho benchmarku.
     */
    private void runBenchmark() {
        for (int y = 0; y < FRAME_HEIGHT; y += 10) {
            for (int x = 0; x < FRAME_WIDTH; x += 10) {
                setImageX(x);
                setImageY(y);
                // vynutime si prekresleni okna
                repaint();
                try {
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * Vytvoreni testovaciho obrazku.
     */
    private void createSpriteImage() {
        this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT);
        drawIntoSpriteImage(this.spriteImage);
    }
 
    /**
     * Vytvoreni obrazku pozadi.
     */
    private void createBackgroundImage() {
        this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT);
        drawIntoBackgroundImage(this.backgroundImage);
    }
 
    /**
     * Vykresleni vzorku do obrazku.
     */
    private void drawIntoSpriteImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni cerveneho ctverce
        g.setColor(Color.red);
        g.drawRect(0, 0, width-1, height-1);
 
        // vykresleni krize
        g.setColor(Color.blue);
        g.drawLine(width >> 1, height/3, width >> 1, 2*height/3);
        g.drawLine(width/3, height >> 1, 2*width/3, height >> 1);
        g.dispose();
    }
 
    /**
     * Vykresleni vzorku do obrazku na pozadi.
     */
    private void drawIntoBackgroundImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni zelene sachovnice
        g.setColor(Color.green);
        for (int y = 0; y < height; y += 20) {
            g.drawLine(0, y, width - 1, y);
        }
        g.setColor(Color.green);
        for (int x = 0; x < width; x += 20) {
            g.drawLine(x, 0, x, height - 1);
        }
 
        g.dispose();
    }
 
    /**
     * Nastaveni parametru okna a zobrazeni.
     * @param gd 
     */
    private void showJFrame(GraphicsDevice gd) {
        // vypneme okraje a dalsi zbytecnosti :)
        this.setUndecorated(true);
        this.setLocation(0, 0);
        this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.createBufferStrategy(1);
        this.setVisible(true);
        gd.setFullScreenWindow(this);
    }
 
    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
 
        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();
 
        do {
            if (this.offscreen == null) {
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
            int valid = this.offscreen.validate(graphicsConfiguration);
 
            if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
                // zmena grafickeho rezimu?
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
 
            // vykresleni pozadi
            this.offscreen.getGraphics().drawImage(background, 0, 0, null);
            // vykresleni spritu
            this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null);
            // prenos na obrazovku
            g.drawImage(this.offscreen, 0, 0, null);
            g.dispose();
        } while (this.offscreen.contentsLost());
        this.repaintCount ++;
    }
 
    /**
     * Nastaveni horizontalni souradnice obrazku.
     *
     * @param x
     */
    void setImageX(int x) {
        this.imageX = x;
    }
 
    /**
     * Nastaveni vertikalni souradnice obrazku.
     *
     * @param y
     */
    void setImageY(int y) {
        this.imageY = y;
    }
 
    /**
     * Getter pro atribut imageX.
     */
    int getImageX() {
        return this.imageX;
    }
 
    /**
     * Getter pro atribut imageY.
     */
    int getImageY() {
        return this.imageY;
    }
 
    /**
     * Getter pro atribut spriteImage.
     */
    BufferedImage getSpriteImage() {
        return this.spriteImage;
    }
 
    /**
     * Getter pro atribut backgroundImage.
     */
    BufferedImage getBackgroundImage() {
        return this.backgroundImage;
    }
 
    /**
     * Spusteni testu.
     * @param args
     */
    public static void main(String[] args) {
        new BlitTest4().run();
    }
 
}

8. Problematika průhledných obrázků

Pro zajímavost se ještě podívejme na problematiku vykreslení průhledných (resp. přesněji řečeno poloprůhledných) obrázků. Předchozí testovací příklad upravíme takovým způsobem, aby byl sprite vykreslen poloprůhledně, tj. aby pod ním částečně prosvítalo pozadí. Vykreslování poloprůhledných obrázků je již v současnosti realizováno funkcemi nabízenými grafickými akcelerátory, takže by vlastní práce s těmito obrázky neměla příliš zatěžovat mikroprocesor. Nejdříve se podívejme, jak se vytvoří bitmapa typu BufferedImage s podporou (polo)průhlednosti. Je to snadné, musíme pouze přidat další parametr do metody GraphicsConfiguration.cre­ateCompatibleImage():

    /**
     * Vytvoreni testovaciho obrazku.
     */
    private void createSpriteImage() {
        this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT, Transparency.TRANSLUCENT);
        drawIntoSpriteImage(this.spriteImage);
    }

Zajímavější je způsob vykreslení poloprůhledné bitmapy. Zde využijeme metodu Graphics2D.setComposite() pro nastavení operace prováděné při vykreslování, které se předá instance třídy AlphaComposite. Důležité je, že operace typu SRC_OVER je (na rozdíl od některých dalších rastrových operací) skutečně akcelerovaná. Druhým parametrem metody AlphaComposite.getInstance() je hodnota průhlednosti, což je reálné číslo v rozsahu 0..1 odpovídající 0 až 100%:

    // vykresleni polopruhledneho spritu
    float transparency = 0.8f;
    Graphics2D g2 = (Graphics2D)this.offscreen.getGraphics();
    g2.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, transparency));
    g2.drawImage(sprite, spriteX, spriteY, null);

Nová varianta překreslovací rutiny nyní vypadá následovně:

CS24_early

    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
 
        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();
 
        do {
            if (this.offscreen == null) {
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
            int valid = this.offscreen.validate(graphicsConfiguration);
 
            if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
                // zmena grafickeho rezimu?
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
 
            // vykresleni pozadi
            this.offscreen.getGraphics().drawImage(background, 0, 0, null);
            // vykresleni polopruhledneho spritu
            float transparency = 0.8f;
            Graphics2D g2 = (Graphics2D)this.offscreen.getGraphics();
            g2.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, transparency));
            g2.drawImage(sprite, spriteX, spriteY, null);
            // prenos na obrazovku
            g.drawImage(this.offscreen, 0, 0, null);
            g.dispose();
        } while (this.offscreen.contentsLost());
        this.repaintCount ++;
    }

9. Čtvrtý demonstrační příklad: vykreslení pozadí a poloprůhledného spritu do bufferu tvořeného bitmapou typu VolatileImage

Vykreslení průhledného spritu na neprůhledném pozadí je realizováno v dnešním čtvrtém a současně i posledním demonstračním příkladu, jehož úplný zdrojový kód je vypsán pod tímto odstavcem:

import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
 
import javax.swing.JFrame;
import javax.swing.WindowConstants;
 
/**
 * Jednoduchy test metody Graphics.drawImage().
 * Pri vykreslovani se nebude pouzivat double buffering.
 * Predbezne vykreslovani se bude provadet do volatilniho obrazku.
 * Sprite bude vykreslen polopruhledne.
 */
public class BlitTest5 extends JFrame {
 
    /**
     * Generated serial version UID.
     */
    private static final long serialVersionUID = -6455857608125974217L;
 
    /**
     * Sirka testovaciho obrazku.
     */
    private static final int IMAGE_WIDTH = 256;
 
    /**
     * Vyska testovaciho obrazku.
     */
    private static final int IMAGE_HEIGHT = 256;
 
    /**
     * Sirka okna ve fullscreen rezimu.
     */
    private static final int FRAME_WIDTH = 1024;
 
    /**
     * Vyska okna ve fullscreen rezimu.
     */
    private static final int FRAME_HEIGHT = 768;
 
    /**
     * Bitova hloubka nastavovaneho grafickeho rezimu.
     */
    private static final int BPP = 16;
 
    /**
     * Horizontalni souradnice obrazku.
     */
    private int imageX;
 
    /**
     * Vertikalni souradnice obrazku.
     */
    private int imageY;
 
    /**
     * Pocet skutecnych volani metody paint()
     */
    private int repaintCount = 0;
 
    /**
     * Obrazek, ktery se bude prenaset do okna.
     */
    private BufferedImage spriteImage = null;
 
    /**
     * Obrazek zobrazeny na pozadi okna.
     */
    private BufferedImage backgroundImage = null;
 
    /**
     * Volatilni obrazek pouzity pro kresleni mimo obrazovku.
     */
    private VolatileImage offscreen = null;
 
    /**
     * Spusteni testu.
     */
    private void run() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
 
        showJFrame(gd);
        setDisplayMode(gd);
        createSpriteImage();
        createBackgroundImage();
        runBenchmark();
 
        gd.setFullScreenWindow(null);
        this.dispose();
        System.out.println("Repaint count: " + this.repaintCount);
        System.exit(0);
    }
 
    /**
     * Nastaveni grafickeho rezimu.
     */
    private void setDisplayMode(GraphicsDevice gd) {
        DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN);
        gd.setDisplayMode(dm);
    }
 
    /**
     * Spusteni vlastniho benchmarku.
     */
    private void runBenchmark() {
        for (int y = 0; y < FRAME_HEIGHT; y += 10) {
            for (int x = 0; x < FRAME_WIDTH; x += 10) {
                setImageX(x);
                setImageY(y);
                // vynutime si prekresleni okna
                repaint();
                try {
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * Vytvoreni testovaciho obrazku.
     */
    private void createSpriteImage() {
        this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT, Transparency.TRANSLUCENT);
        drawIntoSpriteImage(this.spriteImage);
    }
 
    /**
     * Vytvoreni obrazku pozadi.
     */
    private void createBackgroundImage() {
        this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT);
        drawIntoBackgroundImage(this.backgroundImage);
    }
 
    /**
     * Vykresleni vzorku do obrazku.
     */
    private void drawIntoSpriteImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni cerveneho ctverce
        g.setColor(Color.red);
        g.drawRect(0, 0, width-1, height-1);
 
        // vykresleni krize
        g.setColor(Color.blue);
        g.drawLine(width >> 1, height/3, width >> 1, 2*height/3);
        g.drawLine(width/3, height >> 1, 2*width/3, height >> 1);
        g.dispose();
    }
 
    /**
     * Vykresleni vzorku do obrazku na pozadi.
     */
    private void drawIntoBackgroundImage(BufferedImage image) {
        final int width = image.getWidth();
        final int height = image.getHeight();
        Graphics2D g = image.createGraphics();
 
        // smazeme pozadi obrazku
        g.setColor(Color.white);
        g.fillRect(0, 0, width-1, height-1);
 
        // vykresleni zelene sachovnice
        g.setColor(Color.green);
        for (int y = 0; y < height; y += 20) {
            g.drawLine(0, y, width - 1, y);
        }
        g.setColor(Color.green);
        for (int x = 0; x < width; x += 20) {
            g.drawLine(x, 0, x, height - 1);
        }
 
        g.dispose();
    }
 
    /**
     * Nastaveni parametru okna a zobrazeni.
     * @param gd 
     */
    private void showJFrame(GraphicsDevice gd) {
        // vypneme okraje a dalsi zbytecnosti :)
        this.setUndecorated(true);
        this.setLocation(0, 0);
        this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.createBufferStrategy(1);
        this.setVisible(true);
        gd.setFullScreenWindow(this);
    }
 
    @Override
    public void paint(Graphics g) {
        final BufferedImage sprite = getSpriteImage();
        final BufferedImage background = getBackgroundImage();
        if (sprite == null || background == null) return;
        final int spriteX = getImageX() - sprite.getWidth() / 2;
        final int spriteY = getImageY() - sprite.getHeight() / 2;
 
        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();
 
        do {
            if (this.offscreen == null) {
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
            int valid = this.offscreen.validate(graphicsConfiguration);
 
            if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
                // zmena grafickeho rezimu?
                this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT);
            }
 
            // vykresleni pozadi
            this.offscreen.getGraphics().drawImage(background, 0, 0, null);
            // vykresleni polopruhledneho spritu
            float transparency = 0.8f;
            Graphics2D g2 = (Graphics2D)this.offscreen.getGraphics();
            g2.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, transparency));
            g2.drawImage(sprite, spriteX, spriteY, null);
            // prenos na obrazovku
            g.drawImage(this.offscreen, 0, 0, null);
            g.dispose();
        } while (this.offscreen.contentsLost());
        this.repaintCount ++;
    }
 
    /**
     * Nastaveni horizontalni souradnice obrazku.
     *
     * @param x
     */
    void setImageX(int x) {
        this.imageX = x;
    }
 
    /**
     * Nastaveni vertikalni souradnice obrazku.
     *
     * @param y
     */
    void setImageY(int y) {
        this.imageY = y;
    }
 
    /**
     * Getter pro atribut imageX.
     */
    int getImageX() {
        return this.imageX;
    }
 
    /**
     * Getter pro atribut imageY.
     */
    int getImageY() {
        return this.imageY;
    }
 
    /**
     * Getter pro atribut spriteImage.
     */
    BufferedImage getSpriteImage() {
        return this.spriteImage;
    }
 
    /**
     * Getter pro atribut backgroundImage.
     */
    BufferedImage getBackgroundImage() {
        return this.backgroundImage;
    }
 
    /**
     * Spusteni testu.
     * @param args
     */
    public static void main(String[] args) {
        new BlitTest5().run();
    }
 
}

10. Repositář se zdrojovými soubory dnešních demonstračních příkladů

Následují již tradiční odkazy na zdrojové kódy uložené do Mercurial repositáře. V následující tabulce najdete linky na prozatím nejnovější verze všech čtyř dnes popsaných demonstračních příkladů vykreslujícího bitmapy typu BufferedImageVolatileImage v celoobrazovkovém grafickém režimu:

11. Odkazy na Internetu

  1. Javadoc – třída GraphicsDevice
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsDevice.html
  2. Javadoc – třída GraphicsEnvironment
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsEnvironment.html
  3. Javadoc – třída GraphicsConfiguration
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsConfiguration.html
  4. Javadoc – třída DisplayMode
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Dis­playMode.html
  5. Lesson: Full-Screen Exclusive Mode API
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/
  6. Full-Screen Exclusive Mode
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/exclusivemode.html
  7. Display Mode
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/displaymode.html
  8. Using the Full-Screen Exclusive Mode API in Java
    http://www.developer.com/ja­va/other/article.php/3609776/U­sing-the-Full-Screen-Exclusive-Mode-API-in-Java.htm
  9. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  10. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  11. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  12. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  13. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  14. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  15. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  16. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  17. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  18. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  19. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  20. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  21. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  22. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  23. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  24. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  25. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  26. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  27. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  28. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  29. ASM Home page
    http://asm.ow2.org/
  30. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  31. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  32. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  33. BCEL Home page
    http://commons.apache.org/bcel/
  34. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  35. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  36. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  37. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  38. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  39. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  40. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  41. Javassist
    http://www.jboss.org/javassist/
  42. Byteman
    http://www.jboss.org/byteman
  43. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  44. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  45. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  46. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  47. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  48. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  49. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  50. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  51. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  52. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  53. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  54. Cobertura
    http://cobertura.sourceforge.net/
  55. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.