Package com.badlogic.gdx.graphics.g2d

Source Code of com.badlogic.gdx.graphics.g2d.TextureAtlas

/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* 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.badlogic.gdx.graphics.g2d;

import static com.badlogic.gdx.graphics.Texture.TextureWrap.*;

import com.badlogic.gdx.Files.FileType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Page;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Region;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectMap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

/** Loads images from texture atlases created by TexturePacker.<br>
* <br>
* A TextureAtlas must be disposed to free up the resources consumed by the backing textures.
* @author Nathan Sweet */
public class TextureAtlas implements Disposable {
  static final String[] tuple = new String[4];

  private final HashSet<Texture> textures = new HashSet(4);
  private final Array<AtlasRegion> regions = new Array<AtlasRegion>();

  public static class TextureAtlasData {
    public static class Page {
      public final FileHandle textureFile;
      public Texture texture;
      public final boolean useMipMaps;
      public final Format format;
      public final TextureFilter minFilter;
      public final TextureFilter magFilter;
      public final TextureWrap uWrap;
      public final TextureWrap vWrap;

      public Page (FileHandle handle, boolean useMipMaps, Format format, TextureFilter minFilter, TextureFilter magFilter,
        TextureWrap uWrap, TextureWrap vWrap) {
        this.textureFile = handle;
        this.useMipMaps = useMipMaps;
        this.format = format;
        this.minFilter = minFilter;
        this.magFilter = magFilter;
        this.uWrap = uWrap;
        this.vWrap = vWrap;
      }
    }

    public static class Region {
      public Page page;
      public int index;
      public String name;
      public float offsetX;
      public float offsetY;
      public int originalWidth;
      public int originalHeight;
      public boolean rotate;
      public int left;
      public int top;
      public int width;
      public int height;
      public boolean flip;
      public int[] splits;
      public int[] pads;
    }

    final Array<Page> pages = new Array<Page>();
    final Array<Region> regions = new Array<Region>();

    public TextureAtlasData (FileHandle packFile, FileHandle imagesDir, boolean flip) {
      BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 64);
      try {
        Page pageImage = null;
        while (true) {
          String line = reader.readLine();
          if (line == null) break;
          if (line.trim().length() == 0)
            pageImage = null;
          else if (pageImage == null) {
            FileHandle file = imagesDir.child(line);

            Format format = Format.valueOf(readValue(reader));

            readTuple(reader);
            TextureFilter min = TextureFilter.valueOf(tuple[0]);
            TextureFilter max = TextureFilter.valueOf(tuple[1]);

            String direction = readValue(reader);
            TextureWrap repeatX = ClampToEdge;
            TextureWrap repeatY = ClampToEdge;
            if (direction.equals("x"))
              repeatX = Repeat;
            else if (direction.equals("y"))
              repeatY = Repeat;
            else if (direction.equals("xy")) {
              repeatX = Repeat;
              repeatY = Repeat;
            }

            pageImage = new Page(file, min.isMipMap(), format, min, max, repeatX, repeatY);
            pages.add(pageImage);
          } else {
            boolean rotate = Boolean.valueOf(readValue(reader));

            readTuple(reader);
            int left = Integer.parseInt(tuple[0]);
            int top = Integer.parseInt(tuple[1]);

            readTuple(reader);
            int width = Integer.parseInt(tuple[0]);
            int height = Integer.parseInt(tuple[1]);

            Region region = new Region();
            region.page = pageImage;
            region.left = left;
            region.top = top;
            region.width = width;
            region.height = height;
            region.name = line;
            region.rotate = rotate;

            if (readTuple(reader) == 4) { // split is optional
              region.splits = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]),
                Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])};

              if (readTuple(reader) == 4) { // pad is optional, but only present with splits
                region.pads = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]),
                  Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])};

                readTuple(reader);
              }
            }

            region.originalWidth = Integer.parseInt(tuple[0]);
            region.originalHeight = Integer.parseInt(tuple[1]);

            readTuple(reader);
            region.offsetX = Integer.parseInt(tuple[0]);
            region.offsetY = Integer.parseInt(tuple[1]);

            region.index = Integer.parseInt(readValue(reader));

            if (flip) region.flip = true;

            regions.add(region);
          }
        }
      } catch (Exception ex) {
        throw new GdxRuntimeException("Error reading pack file: " + packFile, ex);
      } finally {
        try {
          reader.close();
        } catch (IOException ignored) {
        }
      }

      regions.sort(indexComparator);
    }

    public Array<Page> getPages () {
      return pages;
    }

    public Array<Region> getRegions () {
      return regions;
    }
  }

  /** Creates an empty atlas to which regions can be added. */
  public TextureAtlas () {
  }

  /** Loads the specified pack file using {@link FileType#Internal}, using the parent directory of the pack file to find the page
   * images. */
  public TextureAtlas (String internalPackFile) {
    this(Gdx.files.internal(internalPackFile));
  }

  /** Loads the specified pack file, using the parent directory of the pack file to find the page images. */
  public TextureAtlas (FileHandle packFile) {
    this(packFile, packFile.parent());
  }

  /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner.
   * @see #TextureAtlas(FileHandle) */
  public TextureAtlas (FileHandle packFile, boolean flip) {
    this(packFile, packFile.parent(), flip);
  }

  public TextureAtlas (FileHandle packFile, FileHandle imagesDir) {
    this(packFile, imagesDir, false);
  }

  /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. */
  public TextureAtlas (FileHandle packFile, FileHandle imagesDir, boolean flip) {
    this(new TextureAtlasData(packFile, imagesDir, flip));
  }

  public TextureAtlas (TextureAtlasData data) {
    load(data);
  }

  private void load (TextureAtlasData data) {
    ObjectMap<Page, Texture> pageToTexture = new ObjectMap<Page, Texture>();
    for (Page page : data.pages) {
      Texture texture = null;
      if (page.texture == null) {
        texture = new Texture(page.textureFile, page.format, page.useMipMaps);
        texture.setFilter(page.minFilter, page.magFilter);
        texture.setWrap(page.uWrap, page.vWrap);
      } else {
        texture = page.texture;
        texture.setFilter(page.minFilter, page.magFilter);
        texture.setWrap(page.uWrap, page.vWrap);
      }
      textures.add(texture);
      pageToTexture.put(page, texture);
    }

    for (Region region : data.regions) {
      int width = region.width;
      int height = region.height;
      AtlasRegion atlasRegion = new AtlasRegion(pageToTexture.get(region.page), region.left, region.top,
        region.rotate ? height : width, region.rotate ? width : height);
      atlasRegion.index = region.index;
      atlasRegion.name = region.name;
      atlasRegion.offsetX = region.offsetX;
      atlasRegion.offsetY = region.offsetY;
      atlasRegion.originalHeight = region.originalHeight;
      atlasRegion.originalWidth = region.originalWidth;
      atlasRegion.rotate = region.rotate;
      atlasRegion.splits = region.splits;
      atlasRegion.pads = region.pads;
      if (region.flip) atlasRegion.flip(false, true);
      regions.add(atlasRegion);
    }
  }

  /** Adds a region to the atlas. The specified texture will be disposed when the atlas is disposed. */
  public AtlasRegion addRegion (String name, Texture texture, int x, int y, int width, int height) {
    textures.add(texture);
    AtlasRegion region = new AtlasRegion(texture, x, y, width, height);
    region.name = name;
    region.originalWidth = width;
    region.originalHeight = height;
    region.index = -1;
    regions.add(region);
    return region;
  }

  /** Adds a region to the atlas. The texture for the specified region will be disposed when the atlas is disposed. */
  public AtlasRegion addRegion (String name, TextureRegion textureRegion) {
    return addRegion(name, textureRegion.texture, textureRegion.getRegionX(), textureRegion.getRegionY(),
      textureRegion.getRegionWidth(), textureRegion.getRegionHeight());
  }

  /** Returns all regions in the atlas. */
  public Array<AtlasRegion> getRegions () {
    return regions;
  }

  /** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
   * should be cached rather than calling this method multiple times.
   * @return The region, or null. */
  public AtlasRegion findRegion (String name) {
    for (int i = 0, n = regions.size; i < n; i++)
      if (regions.get(i).name.equals(name)) return regions.get(i);
    return null;
  }

  /** Returns the first region found with the specified name and index. This method uses string comparison to find the region, so
   * the result should be cached rather than calling this method multiple times.
   * @return The region, or null. */
  public AtlasRegion findRegion (String name, int index) {
    for (int i = 0, n = regions.size; i < n; i++) {
      AtlasRegion region = regions.get(i);
      if (!region.name.equals(name)) continue;
      if (region.index != index) continue;
      return region;
    }
    return null;
  }

  /** Returns all regions with the specified name, ordered by smallest to largest {@link AtlasRegion#index index}. This method
   * uses string comparison to find the regions, so the result should be cached rather than calling this method multiple times. */
  public Array<AtlasRegion> findRegions (String name) {
    Array<AtlasRegion> matched = new Array();
    for (int i = 0, n = regions.size; i < n; i++) {
      AtlasRegion region = regions.get(i);
      if (region.name.equals(name)) matched.add(new AtlasRegion(region));
    }
    return matched;
  }

  /** Returns all regions in the atlas as sprites. This method creates a new sprite for each region, so the result should be
   * stored rather than calling this method multiple times.
   * @see #createSprite(String) */
  public Array<Sprite> createSprites () {
    Array sprites = new Array(regions.size);
    for (int i = 0, n = regions.size; i < n; i++)
      sprites.add(newSprite(regions.get(i)));
    return sprites;
  }

  /** Returns the first region found with the specified name as a sprite. If whitespace was stripped from the region when it was
   * packed, the sprite is automatically positioned as if whitespace had not been stripped. This method uses string comparison to
   * find the region and constructs a new sprite, so the result should be cached rather than calling this method multiple times.
   * @return The sprite, or null. */
  public Sprite createSprite (String name) {
    for (int i = 0, n = regions.size; i < n; i++)
      if (regions.get(i).name.equals(name)) return newSprite(regions.get(i));
    return null;
  }

  /** Returns the first region found with the specified name and index as a sprite. This method uses string comparison to find the
   * region and constructs a new sprite, so the result should be cached rather than calling this method multiple times.
   * @return The sprite, or null.
   * @see #createSprite(String) */
  public Sprite createSprite (String name, int index) {
    for (int i = 0, n = regions.size; i < n; i++) {
      AtlasRegion region = regions.get(i);
      if (!region.name.equals(name)) continue;
      if (region.index != index) continue;
      return newSprite(regions.get(i));
    }
    return null;
  }

  /** Returns all regions with the specified name as sprites, ordered by smallest to largest {@link AtlasRegion#index index}. This
   * method uses string comparison to find the regions and constructs new sprites, so the result should be cached rather than
   * calling this method multiple times.
   * @see #createSprite(String) */
  public Array<Sprite> createSprites (String name) {
    Array<Sprite> matched = new Array();
    for (int i = 0, n = regions.size; i < n; i++) {
      AtlasRegion region = regions.get(i);
      if (region.name.equals(name)) matched.add(newSprite(region));
    }
    return matched;
  }

  private Sprite newSprite (AtlasRegion region) {
    if (region.packedWidth == region.originalWidth && region.packedHeight == region.originalHeight) {
      if (region.rotate) {
        Sprite sprite = new Sprite(region);
        sprite.setBounds(0, 0, region.getRegionHeight(), region.getRegionWidth());
        sprite.rotate90(true);
        return sprite;
      }
      return new Sprite(region);
    }
    return new AtlasSprite(region);
  }

  /** Returns the first region found with the specified name as a {@link NinePatch}. The region must have been packed with
   * ninepatch splits. This method uses string comparison to find the region and constructs a new ninepatch, so the result should
   * be cached rather than calling this method multiple times.
   * @return The ninepatch, or null. */
  public NinePatch createPatch (String name) {
    for (int i = 0, n = regions.size; i < n; i++) {
      AtlasRegion region = regions.get(i);
      if (region.name.equals(name)) {
        int[] splits = region.splits;
        if (splits == null) throw new IllegalArgumentException("Region does not have ninepatch splits: " + name);
        NinePatch patch = new NinePatch(region, splits[0], splits[1], splits[2], splits[3]);
        if (region.pads != null) patch.setPadding(region.pads[0], region.pads[1], region.pads[2], region.pads[3]);
        return patch;
      }
    }
    return null;
  }

  /** @return the textures of the pages, unordered */
  public Set<Texture> getTextures () {
    return textures;
  }

  /** Releases all resources associated with this TextureAtlas instance. This releases all the textures backing all TextureRegions
   * and Sprites, which should no longer be used after calling dispose. */
  public void dispose () {
    for (Texture texture : textures)
      texture.dispose();
    textures.clear();
  }

  static final Comparator<Region> indexComparator = new Comparator<Region>() {
    public int compare (Region region1, Region region2) {
      int i1 = region1.index;
      if (i1 == -1) i1 = Integer.MAX_VALUE;
      int i2 = region2.index;
      if (i2 == -1) i2 = Integer.MAX_VALUE;
      return i1 - i2;
    }
  };

  static String readValue (BufferedReader reader) throws IOException {
    String line = reader.readLine();
    int colon = line.indexOf(':');
    if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line);
    return line.substring(colon + 1).trim();
  }

  /** Returns the number of tuple values read (2 or 4). */
  static int readTuple (BufferedReader reader) throws IOException {
    String line = reader.readLine();
    int colon = line.indexOf(':');
    if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line);
    int i = 0, lastMatch = colon + 1;
    for (i = 0; i < 3; i++) {
      int comma = line.indexOf(',', lastMatch);
      if (comma == -1) {
        if (i == 0) throw new GdxRuntimeException("Invalid line: " + line);
        break;
      }
      tuple[i] = line.substring(lastMatch, comma).trim();
      lastMatch = comma + 1;
    }
    tuple[i] = line.substring(lastMatch).trim();
    return i + 1;
  }

  /** Describes the region of a packed image and provides information about the original image before it was packed. */
  static public class AtlasRegion extends TextureRegion {
    /** The number at the end of the original image file name, or -1 if none.<br>
     * <br>
     * When sprites are packed, if the original file name ends with a number, it is stored as the index and is not considered as
     * part of the sprite's name. This is useful for keeping animation frames in order.
     * @see TextureAtlas#findRegions(String) */
    public int index;

    /** The name of the original image file, up to the first underscore. Underscores denote special instructions to the texture
     * packer. */
    public String name;

    /** The offset from the left of the original image to the left of the packed image, after whitespace was removed for packing. */
    public float offsetX;

    /** The offset from the bottom of the original image to the bottom of the packed image, after whitespace was removed for
     * packing. */
    public float offsetY;

    /** The width of the image, after whitespace was removed for packing. */
    public int packedWidth;

    /** The height of the image, after whitespace was removed for packing. */
    public int packedHeight;

    /** The width of the image, before whitespace was removed and rotation was applied for packing. */
    public int originalWidth;

    /** The height of the image, before whitespace was removed for packing. */
    public int originalHeight;

    /** If true, the region has been rotated 90 degrees counter clockwise. */
    public boolean rotate;

    /** The ninepatch splits, or null if not a ninepatch. Has 4 elements: left, right, top, bottom. */
    public int[] splits;

    /** The ninepatch pads, or null if not a ninepatch or the has no padding. Has 4 elements: left, right, top, bottom. */
    public int[] pads;

    public AtlasRegion (Texture texture, int x, int y, int width, int height) {
      super(texture, x, y, width, height);
      packedWidth = width;
      packedHeight = height;
    }

    public AtlasRegion (AtlasRegion region) {
      setRegion(region);
      index = region.index;
      name = region.name;
      offsetX = region.offsetX;
      offsetY = region.offsetY;
      packedWidth = region.packedWidth;
      packedHeight = region.packedHeight;
      originalWidth = region.originalWidth;
      originalHeight = region.originalHeight;
      rotate = region.rotate;
      splits = region.splits;
    }

    /** Flips the region, adjusting the offset so the image appears to be flip as if no whitespace has been removed for packing. */
    public void flip (boolean x, boolean y) {
      super.flip(x, y);
      if (x) offsetX = originalWidth - offsetX - getRotatedPackedWidth();
      if (y) offsetY = originalHeight - offsetY - getRotatedPackedHeight();
    }

    /** Returns the packed width considering the rotate value, if it is true then it returns the packedHeight, otherwise it
     * returns the packedWidth. */
    public float getRotatedPackedWidth () {
      return rotate ? packedHeight : packedWidth;
    }

    /** Returns the packed height considering the rotate value, if it is true then it returns the packedWidth, otherwise it
     * returns the packedHeight. */
    public float getRotatedPackedHeight () {
      return rotate ? packedWidth : packedHeight;
    }
  }

  /** A sprite that, if whitespace was stripped from the region when it was packed, is automatically positioned as if whitespace
   * had not been stripped. */
  static public class AtlasSprite extends Sprite {
    final AtlasRegion region;
    float originalOffsetX, originalOffsetY;

    public AtlasSprite (AtlasRegion region) {
      this.region = new AtlasRegion(region);
      originalOffsetX = region.offsetX;
      originalOffsetY = region.offsetY;
      setRegion(region);
      setOrigin(region.originalWidth / 2f, region.originalHeight / 2f);
      int width = region.getRegionWidth();
      int height = region.getRegionHeight();
      if (region.rotate) {
        super.rotate90(true);
        super.setBounds(region.offsetX, region.offsetY, height, width);
      } else
        super.setBounds(region.offsetX, region.offsetY, width, height);
      setColor(1, 1, 1, 1);
    }

    public AtlasSprite (AtlasSprite sprite) {
      region = sprite.region;
      this.originalOffsetX = sprite.originalOffsetX;
      this.originalOffsetY = sprite.originalOffsetY;
      set(sprite);
    }

    public void setPosition (float x, float y) {
      super.setPosition(x + region.offsetX, y + region.offsetY);
    }

    public void setBounds (float x, float y, float width, float height) {
      float widthRatio = width / region.originalWidth;
      float heightRatio = height / region.originalHeight;
      region.offsetX = originalOffsetX * widthRatio;
      region.offsetY = originalOffsetY * heightRatio;
      int packedWidth = region.rotate ? region.packedHeight : region.packedWidth;
      int packedHeight = region.rotate ? region.packedWidth : region.packedHeight;
      super.setBounds(x + region.offsetX, y + region.offsetY, packedWidth * widthRatio, packedHeight * heightRatio);
    }

    public void setSize (float width, float height) {
      setBounds(getX(), getY(), width, height);
    }

    public void setOrigin (float originX, float originY) {
      super.setOrigin(originX - region.offsetX, originY - region.offsetY);
    }

    public void flip (boolean x, boolean y) {
      // Flip texture.

      super.flip(x, y);

      float oldOriginX = getOriginX();
      float oldOriginY = getOriginY();
      float oldOffsetX = region.offsetX;
      float oldOffsetY = region.offsetY;

      float widthRatio = getWidthRatio();
      float heightRatio = getHeightRatio();

      region.offsetX = originalOffsetX;
      region.offsetY = originalOffsetY;
      region.flip(x, y); // Updates x and y offsets.
      originalOffsetX = region.offsetX;
      originalOffsetY = region.offsetY;
      region.offsetX *= widthRatio;
      region.offsetY *= heightRatio;

      // Update position and origin with new offsets.
      translate(region.offsetX - oldOffsetX, region.offsetY - oldOffsetY);
      setOrigin(oldOriginX, oldOriginY);
    }

    public void rotate90 (boolean clockwise) {
      // Rotate texture.
      super.rotate90(clockwise);

      float oldOriginX = getOriginX();
      float oldOriginY = getOriginY();
      float oldOffsetX = region.offsetX;
      float oldOffsetY = region.offsetY;

      float widthRatio = getWidthRatio();
      float heightRatio = getHeightRatio();

      if (clockwise) {
        region.offsetX = oldOffsetY;
        region.offsetY = region.originalHeight * heightRatio - oldOffsetX - region.packedWidth * widthRatio;
      } else {
        region.offsetX = region.originalWidth * widthRatio - oldOffsetY - region.packedHeight * heightRatio;
        region.offsetY = oldOffsetX;
      }

      // Update position and origin with new offsets.
      translate(region.offsetX - oldOffsetX, region.offsetY - oldOffsetY);
      setOrigin(oldOriginX, oldOriginY);
    }

    public float getX () {
      return super.getX() - region.offsetX;
    }

    public float getY () {
      return super.getY() - region.offsetY;
    }

    public float getOriginX () {
      return super.getOriginX() + region.offsetX;
    }

    public float getOriginY () {
      return super.getOriginY() + region.offsetY;
    }

    public float getWidth () {
      return super.getWidth() / region.getRotatedPackedWidth() * region.originalWidth;
    }

    public float getHeight () {
      return super.getHeight() / region.getRotatedPackedHeight() * region.originalHeight;
    }

    public float getWidthRatio () {
      return super.getWidth() / region.getRotatedPackedWidth();
    }

    public float getHeightRatio () {
      return super.getHeight() / region.getRotatedPackedHeight();
    }

    public AtlasRegion getAtlasRegion () {
      return region;
    }
  }
}
TOP

Related Classes of com.badlogic.gdx.graphics.g2d.TextureAtlas

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.