com.ridiculousRPG.util.TextureRegionLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.ridiculousRPG.util.TextureRegionLoader.java

Source

/*
 * Copyright 2011 Alexander Baumgartner
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ridiculousRPG.util;

import java.util.HashMap;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Pool;
import com.ridiculousRPG.GameBase;

/**
 * This class is used to load and cache textures. It automatically adds a
 * padding if the textures width or height isn't a power of two.<br>
 * <br>
 * The implementation should perform well. Don't hesitate to use it whenever you
 * need a texture.
 * 
 * @author Alexander Baumgartner
 */
public final class TextureRegionLoader {
    private TextureRegionLoader() {
    }// static container

    static HashMap<String, TextureCache> textureCache = new HashMap<String, TextureCache>(128);
    static HashMap<TextureCache, String> textureReverseCache = new HashMap<TextureCache, String>(128);
    static Pool<TextureRegionRef> textureRegionPool = new Pool<TextureRegionRef>(512, 8192) {
        @Override
        protected TextureRegionRef newObject() {
            return new TextureRegionRef();
        }
    };

    /**
     * This method loads the Pixmap, adds some padding if it's not sized with
     * powers of two, creates a {@link TextureCache} and caches the created
     * Texture. Then it obtains a {@link TextureRegionRef} from the pool and
     * returns the requested {@link TextureRegionRef}
     * 
     * @param internalPath
     *            The path to the picture
     * @param x
     *            top left corner
     * @param y
     *            top left corner
     * @param width
     *            the regions width
     * @param height
     *            the regions height
     * @return A TextureRegion matching the given parameters
     */
    public static TextureRegionRef load(String internalPath, int x, int y, int width, int height) {
        return load(Gdx.files.internal(internalPath), x, y, width, height);
    }

    /**
     * This method loads the Pixmap, adds some padding if it's not sized with
     * powers of two, creates a {@link TextureCache} and caches the created
     * Texture. Then it obtains a {@link TextureRegionRef} from the pool and
     * returns the requested {@link TextureRegionRef}
     * 
     * @param filePath
     *            The file to the picture
     * @param x
     *            top left corner
     * @param y
     *            top left corner
     * @param width
     *            the regions width
     * @param height
     *            the regions height
     * @return A TextureRegion matching the given parameters
     */
    public static TextureRegionRef load(FileHandle filePath, int x, int y, int width, int height) {
        return obtainCache(filePath).obtainRegion(x, y, width, height);
    }

    /**
     * This method loads the Pixmap, adds some padding if it's not sized with
     * powers of two, creates a {@link TextureCache} and caches the created
     * Texture. Then it obtains a {@link TextureRegionRef} from the pool and
     * returns the {@link TextureRegionRef}, which is cropped to the size of the
     * Pixmap.
     * 
     * @param internalPath
     *            The path to the picture
     * @return A TextureRegion matching the given parameters
     */
    public static TextureRegionRef load(String internalPath) {
        return load(Gdx.files.internal(internalPath));
    }

    /**
     * This method loads the Pixmap, adds some padding if it's not sized with
     * powers of two, creates a {@link TextureCache} and caches the created
     * Texture. Then it obtains a {@link TextureRegionRef} from the pool and
     * returns the {@link TextureRegionRef}, which is cropped to the size of the
     * Pixmap.
     * 
     * @param filePath
     *            The file to the picture
     * @return A TextureRegion matching the given parameters
     */
    public static TextureRegionRef load(FileHandle filePath) {
        return obtainCache(filePath).obtainRegion();
    }

    /**
     * Obtains a texture region for drawing {@link Pixmap}s on it.<br>
     * This method creates a texture region with an underlying texture (which is
     * sized with the next powers of two) for drawing.
     * 
     * @param width
     *            the width of the texture region
     * @param height
     *            the height of the texture region
     * @param format
     *            the format for drawing {@link Pixmap}s onto this TextureRegion
     * @return A texture region with the specified width and height for drawing
     *         on it.
     */
    public static TextureRegionRef obtainEmptyRegion(int width, int height, final Format format) {
        final int safeWidth = MathUtils.nextPowerOfTwo(width);
        final int safeHeight = MathUtils.nextPowerOfTwo(height);
        final PixmapTextureData ptd = new PixmapTextureData(new Pixmap(safeWidth, safeHeight, format), null, false,
                true);
        TextureCache tCache;
        if (GameBase.$().isGlContextThread()) {
            tCache = new TextureCache(ptd);
        } else {
            final TextureCacheContainer tCC = new TextureCacheContainer();
            new ExecWithGlContext() {
                @Override
                public void exec() {
                    tCC.tCache = new TextureCache(ptd);
                }
            }.runWait();
            tCache = tCC.tCache;
        }
        return tCache.obtainRegion(0, 0, width, height);
    }

    private static TextureCache obtainCache(FileHandle filePath) {
        String fileName = filePath.path();
        TextureCache tCache = textureCache.get(fileName);
        if (tCache == null) {
            final Pixmap pm = new Pixmap(filePath);
            final int width = pm.getWidth();
            final int height = pm.getHeight();
            final int safeWidth = MathUtils.nextPowerOfTwo(width);
            final int safeHeight = MathUtils.nextPowerOfTwo(height);
            if (width != safeWidth || height != safeHeight) {
                final PixmapTextureData ptd = new PixmapTextureData(
                        new Pixmap(safeWidth, safeHeight, pm.getFormat()), null, false, true);
                if (GameBase.$().isGlContextThread()) {
                    tCache = new TextureCache(ptd);
                    tCache.drawPixmap(pm, true);
                } else {
                    final TextureCacheContainer tCC = new TextureCacheContainer();
                    new ExecWithGlContext() {
                        @Override
                        public void exec() {
                            tCC.tCache = new TextureCache(ptd);
                            tCC.tCache.drawPixmap(pm, true);
                        }
                    }.runWait();
                    tCache = tCC.tCache;
                }
            } else {
                if (GameBase.$().isGlContextThread()) {
                    tCache = new TextureCache(pm, true);
                } else {
                    final TextureCacheContainer tCC = new TextureCacheContainer();
                    new ExecWithGlContext() {
                        @Override
                        public void exec() {
                            tCC.tCache = new TextureCache(pm, true);
                        }
                    }.runWait();
                    tCache = tCC.tCache;
                }
            }
            textureCache.put(fileName, tCache);
            textureReverseCache.put(tCache, fileName);
        }
        return tCache;
    }

    /**
     * Use {@link TextureRegionLoader#load} or
     * {@link TextureRegionLoader#obtainEmptyRegion(int, int, Format)} to obtain
     * a texture region.
     * 
     * @author Alexander Baumgartner
     */
    public static class TextureRegionRef extends TextureRegion implements Disposable {
        /**
         * Use {@link TextureRegionLoader#load} or
         * {@link TextureRegionLoader#obtainEmptyRegion(int, int, Format)} to
         * obtain a texture region.
         */
        protected TextureRegionRef() {
        }

        /**
         * Draws the {@link Pixmap} at position (0,0)
         * 
         * @param pm
         *            The {@link Pixmap} to draw.
         */
        public void draw(Pixmap pm) {
            getTexture().draw(pm, 0, 0);
        }

        /**
         * Draws the {@link Pixmap} at position (x,y)
         * 
         * @param pm
         *            The {@link Pixmap} to draw.
         */
        public void draw(Pixmap pm, int x, int y) {
            getTexture().draw(pm, x, y);
        }

        @Override
        public void dispose() {
            getTexture().dispose();
            textureRegionPool.free(this);
        }
    }

    private static class TextureCacheContainer {
        TextureCache tCache;
    }

    /**
     * Use {@link TextureRegionLoader#load} if possible
     * 
     * @author Alexander Baumgartner
     */
    protected static class TextureCache extends Texture {
        // reference count
        private int count;
        // width of the pixmap
        private int width;
        // height of the pixmap
        private int height;
        // The pixmap can automatically be disposed
        private Pixmap pixmap;

        /**
         * Use {@link TextureRegionLoader#load} if possible
         */
        protected TextureCache(Pixmap pm, boolean autoDisposePixmap) {
            super(pm);
            width = pm.getWidth();
            height = pm.getHeight();
            if (autoDisposePixmap)
                pixmap = pm;
        }

        /**
         * Use {@link TextureRegionLoader#load} if possible
         */
        protected TextureCache(PixmapTextureData ptd) {
            super(ptd);
        }

        /**
         * Sets the specified {@link Pixmap} for this {@link TextureCache}. This
         * method shouldn't be used outside of the implementation, because it
         * effects all {@link TextureRegionRef}s which are already instantiaded
         * from this {@link TextureCache}.
         * 
         * @param pm
         * @param autoDisposePixmap
         */
        protected void drawPixmap(Pixmap pm, boolean autoDisposePixmap) {
            draw(pm, 0, 0);
            if (autoDisposePixmap) {
                if (pixmap != null)
                    pixmap.dispose();
                pixmap = pm;
            }
        }

        /**
         * Returns the entire region, which is sized by the loaded
         * {@link Pixmap}'s size. Normally this should be the entire picture
         * represented by this {@link TextureCache}
         * 
         * @return
         */
        public TextureRegionRef obtainRegion() {
            return obtainRegion(0, 0, width, height);
        }

        /**
         * Retuns the specified region of this texture.
         * 
         * @param x
         * @param y
         * @param width
         * @param height
         * @return
         */
        public TextureRegionRef obtainRegion(int x, int y, int width, int height) {
            count++;
            TextureRegionRef c = textureRegionPool.obtain();
            c.setTexture(this);
            c.setRegion(x, y, width, height);
            return c;
        }

        @Override
        public void draw(Pixmap pm, int x, int y) {
            super.draw(pm, x, y);
            width = pm.getWidth();
            height = pm.getHeight();
        }

        @Override
        public void dispose() {
            count--;
            if (count == 0) {
                textureCache.remove(textureReverseCache.remove(this));
                if (GameBase.$().isGlContextThread()) {
                    super.dispose();
                    if (pixmap != null)
                        pixmap.dispose();
                } else {
                    Gdx.app.postRunnable(new Runnable() {
                        @Override
                        public void run() {
                            TextureCache.super.dispose();
                            if (pixmap != null)
                                pixmap.dispose();
                        }
                    });
                }
                pixmap = null;
            }
        }
    }
}