• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.graphics.g2d;
18 
19 import static com.badlogic.gdx.graphics.Texture.TextureWrap.*;
20 
21 import com.badlogic.gdx.Files.FileType;
22 import com.badlogic.gdx.Gdx;
23 import com.badlogic.gdx.files.FileHandle;
24 import com.badlogic.gdx.graphics.Pixmap.Format;
25 import com.badlogic.gdx.graphics.Texture;
26 import com.badlogic.gdx.graphics.Texture.TextureFilter;
27 import com.badlogic.gdx.graphics.Texture.TextureWrap;
28 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Page;
29 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Region;
30 import com.badlogic.gdx.utils.Array;
31 import com.badlogic.gdx.utils.Disposable;
32 import com.badlogic.gdx.utils.GdxRuntimeException;
33 import com.badlogic.gdx.utils.ObjectMap;
34 import com.badlogic.gdx.utils.ObjectSet;
35 import com.badlogic.gdx.utils.Sort;
36 import com.badlogic.gdx.utils.StreamUtils;
37 
38 import java.io.BufferedReader;
39 import java.io.IOException;
40 import java.io.InputStreamReader;
41 import java.util.Comparator;
42 import java.util.HashSet;
43 import java.util.Set;
44 
45 /** Loads images from texture atlases created by TexturePacker.<br>
46  * <br>
47  * A TextureAtlas must be disposed to free up the resources consumed by the backing textures.
48  * @author Nathan Sweet */
49 public class TextureAtlas implements Disposable {
50 	static final String[] tuple = new String[4];
51 
52 	private final ObjectSet<Texture> textures = new ObjectSet(4);
53 	private final Array<AtlasRegion> regions = new Array();
54 
55 	public static class TextureAtlasData {
56 		public static class Page {
57 			public final FileHandle textureFile;
58 			public Texture texture;
59 			public final float width, height;
60 			public final boolean useMipMaps;
61 			public final Format format;
62 			public final TextureFilter minFilter;
63 			public final TextureFilter magFilter;
64 			public final TextureWrap uWrap;
65 			public final TextureWrap vWrap;
66 
Page(FileHandle handle, float width, float height, boolean useMipMaps, Format format, TextureFilter minFilter, TextureFilter magFilter, TextureWrap uWrap, TextureWrap vWrap)67 			public Page (FileHandle handle, float width, float height, boolean useMipMaps, Format format, TextureFilter minFilter,
68 				TextureFilter magFilter, TextureWrap uWrap, TextureWrap vWrap) {
69 				this.width = width;
70 				this.height = height;
71 				this.textureFile = handle;
72 				this.useMipMaps = useMipMaps;
73 				this.format = format;
74 				this.minFilter = minFilter;
75 				this.magFilter = magFilter;
76 				this.uWrap = uWrap;
77 				this.vWrap = vWrap;
78 			}
79 		}
80 
81 		public static class Region {
82 			public Page page;
83 			public int index;
84 			public String name;
85 			public float offsetX;
86 			public float offsetY;
87 			public int originalWidth;
88 			public int originalHeight;
89 			public boolean rotate;
90 			public int left;
91 			public int top;
92 			public int width;
93 			public int height;
94 			public boolean flip;
95 			public int[] splits;
96 			public int[] pads;
97 		}
98 
99 		final Array<Page> pages = new Array();
100 		final Array<Region> regions = new Array();
101 
TextureAtlasData(FileHandle packFile, FileHandle imagesDir, boolean flip)102 		public TextureAtlasData (FileHandle packFile, FileHandle imagesDir, boolean flip) {
103 			BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 64);
104 			try {
105 				Page pageImage = null;
106 				while (true) {
107 					String line = reader.readLine();
108 					if (line == null) break;
109 					if (line.trim().length() == 0)
110 						pageImage = null;
111 					else if (pageImage == null) {
112 						FileHandle file = imagesDir.child(line);
113 
114 						float width = 0, height = 0;
115 						if (readTuple(reader) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
116 							width = Integer.parseInt(tuple[0]);
117 							height = Integer.parseInt(tuple[1]);
118 							readTuple(reader);
119 						}
120 						Format format = Format.valueOf(tuple[0]);
121 
122 						readTuple(reader);
123 						TextureFilter min = TextureFilter.valueOf(tuple[0]);
124 						TextureFilter max = TextureFilter.valueOf(tuple[1]);
125 
126 						String direction = readValue(reader);
127 						TextureWrap repeatX = ClampToEdge;
128 						TextureWrap repeatY = ClampToEdge;
129 						if (direction.equals("x"))
130 							repeatX = Repeat;
131 						else if (direction.equals("y"))
132 							repeatY = Repeat;
133 						else if (direction.equals("xy")) {
134 							repeatX = Repeat;
135 							repeatY = Repeat;
136 						}
137 
138 						pageImage = new Page(file, width, height, min.isMipMap(), format, min, max, repeatX, repeatY);
139 						pages.add(pageImage);
140 					} else {
141 						boolean rotate = Boolean.valueOf(readValue(reader));
142 
143 						readTuple(reader);
144 						int left = Integer.parseInt(tuple[0]);
145 						int top = Integer.parseInt(tuple[1]);
146 
147 						readTuple(reader);
148 						int width = Integer.parseInt(tuple[0]);
149 						int height = Integer.parseInt(tuple[1]);
150 
151 						Region region = new Region();
152 						region.page = pageImage;
153 						region.left = left;
154 						region.top = top;
155 						region.width = width;
156 						region.height = height;
157 						region.name = line;
158 						region.rotate = rotate;
159 
160 						if (readTuple(reader) == 4) { // split is optional
161 							region.splits = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]),
162 								Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])};
163 
164 							if (readTuple(reader) == 4) { // pad is optional, but only present with splits
165 								region.pads = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]),
166 									Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])};
167 
168 								readTuple(reader);
169 							}
170 						}
171 
172 						region.originalWidth = Integer.parseInt(tuple[0]);
173 						region.originalHeight = Integer.parseInt(tuple[1]);
174 
175 						readTuple(reader);
176 						region.offsetX = Integer.parseInt(tuple[0]);
177 						region.offsetY = Integer.parseInt(tuple[1]);
178 
179 						region.index = Integer.parseInt(readValue(reader));
180 
181 						if (flip) region.flip = true;
182 
183 						regions.add(region);
184 					}
185 				}
186 			} catch (Exception ex) {
187 				throw new GdxRuntimeException("Error reading pack file: " + packFile, ex);
188 			} finally {
189 				StreamUtils.closeQuietly(reader);
190 			}
191 
192 			regions.sort(indexComparator);
193 		}
194 
getPages()195 		public Array<Page> getPages () {
196 			return pages;
197 		}
198 
getRegions()199 		public Array<Region> getRegions () {
200 			return regions;
201 		}
202 	}
203 
204 	/** Creates an empty atlas to which regions can be added. */
TextureAtlas()205 	public TextureAtlas () {
206 	}
207 
208 	/** Loads the specified pack file using {@link FileType#Internal}, using the parent directory of the pack file to find the page
209 	 * images. */
TextureAtlas(String internalPackFile)210 	public TextureAtlas (String internalPackFile) {
211 		this(Gdx.files.internal(internalPackFile));
212 	}
213 
214 	/** Loads the specified pack file, using the parent directory of the pack file to find the page images. */
TextureAtlas(FileHandle packFile)215 	public TextureAtlas (FileHandle packFile) {
216 		this(packFile, packFile.parent());
217 	}
218 
219 	/** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner.
220 	 * @see #TextureAtlas(FileHandle) */
TextureAtlas(FileHandle packFile, boolean flip)221 	public TextureAtlas (FileHandle packFile, boolean flip) {
222 		this(packFile, packFile.parent(), flip);
223 	}
224 
TextureAtlas(FileHandle packFile, FileHandle imagesDir)225 	public TextureAtlas (FileHandle packFile, FileHandle imagesDir) {
226 		this(packFile, imagesDir, false);
227 	}
228 
229 	/** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. */
TextureAtlas(FileHandle packFile, FileHandle imagesDir, boolean flip)230 	public TextureAtlas (FileHandle packFile, FileHandle imagesDir, boolean flip) {
231 		this(new TextureAtlasData(packFile, imagesDir, flip));
232 	}
233 
234 	/** @param data May be null. */
TextureAtlas(TextureAtlasData data)235 	public TextureAtlas (TextureAtlasData data) {
236 		if (data != null) load(data);
237 	}
238 
load(TextureAtlasData data)239 	private void load (TextureAtlasData data) {
240 		ObjectMap<Page, Texture> pageToTexture = new ObjectMap<Page, Texture>();
241 		for (Page page : data.pages) {
242 			Texture texture = null;
243 			if (page.texture == null) {
244 				texture = new Texture(page.textureFile, page.format, page.useMipMaps);
245 				texture.setFilter(page.minFilter, page.magFilter);
246 				texture.setWrap(page.uWrap, page.vWrap);
247 			} else {
248 				texture = page.texture;
249 				texture.setFilter(page.minFilter, page.magFilter);
250 				texture.setWrap(page.uWrap, page.vWrap);
251 			}
252 			textures.add(texture);
253 			pageToTexture.put(page, texture);
254 		}
255 
256 		for (Region region : data.regions) {
257 			int width = region.width;
258 			int height = region.height;
259 			AtlasRegion atlasRegion = new AtlasRegion(pageToTexture.get(region.page), region.left, region.top,
260 				region.rotate ? height : width, region.rotate ? width : height);
261 			atlasRegion.index = region.index;
262 			atlasRegion.name = region.name;
263 			atlasRegion.offsetX = region.offsetX;
264 			atlasRegion.offsetY = region.offsetY;
265 			atlasRegion.originalHeight = region.originalHeight;
266 			atlasRegion.originalWidth = region.originalWidth;
267 			atlasRegion.rotate = region.rotate;
268 			atlasRegion.splits = region.splits;
269 			atlasRegion.pads = region.pads;
270 			if (region.flip) atlasRegion.flip(false, true);
271 			regions.add(atlasRegion);
272 		}
273 	}
274 
275 	/** Adds a region to the atlas. The specified texture will be disposed when the atlas is disposed. */
addRegion(String name, Texture texture, int x, int y, int width, int height)276 	public AtlasRegion addRegion (String name, Texture texture, int x, int y, int width, int height) {
277 		textures.add(texture);
278 		AtlasRegion region = new AtlasRegion(texture, x, y, width, height);
279 		region.name = name;
280 		region.originalWidth = width;
281 		region.originalHeight = height;
282 		region.index = -1;
283 		regions.add(region);
284 		return region;
285 	}
286 
287 	/** Adds a region to the atlas. The texture for the specified region will be disposed when the atlas is disposed. */
addRegion(String name, TextureRegion textureRegion)288 	public AtlasRegion addRegion (String name, TextureRegion textureRegion) {
289 		return addRegion(name, textureRegion.texture, textureRegion.getRegionX(), textureRegion.getRegionY(),
290 			textureRegion.getRegionWidth(), textureRegion.getRegionHeight());
291 	}
292 
293 	/** Returns all regions in the atlas. */
getRegions()294 	public Array<AtlasRegion> getRegions () {
295 		return regions;
296 	}
297 
298 	/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
299 	 * should be cached rather than calling this method multiple times.
300 	 * @return The region, or null. */
findRegion(String name)301 	public AtlasRegion findRegion (String name) {
302 		for (int i = 0, n = regions.size; i < n; i++)
303 			if (regions.get(i).name.equals(name)) return regions.get(i);
304 		return null;
305 	}
306 
307 	/** Returns the first region found with the specified name and index. This method uses string comparison to find the region, so
308 	 * the result should be cached rather than calling this method multiple times.
309 	 * @return The region, or null. */
findRegion(String name, int index)310 	public AtlasRegion findRegion (String name, int index) {
311 		for (int i = 0, n = regions.size; i < n; i++) {
312 			AtlasRegion region = regions.get(i);
313 			if (!region.name.equals(name)) continue;
314 			if (region.index != index) continue;
315 			return region;
316 		}
317 		return null;
318 	}
319 
320 	/** Returns all regions with the specified name, ordered by smallest to largest {@link AtlasRegion#index index}. This method
321 	 * uses string comparison to find the regions, so the result should be cached rather than calling this method multiple times. */
findRegions(String name)322 	public Array<AtlasRegion> findRegions (String name) {
323 		Array<AtlasRegion> matched = new Array();
324 		for (int i = 0, n = regions.size; i < n; i++) {
325 			AtlasRegion region = regions.get(i);
326 			if (region.name.equals(name)) matched.add(new AtlasRegion(region));
327 		}
328 		return matched;
329 	}
330 
331 	/** Returns all regions in the atlas as sprites. This method creates a new sprite for each region, so the result should be
332 	 * stored rather than calling this method multiple times.
333 	 * @see #createSprite(String) */
createSprites()334 	public Array<Sprite> createSprites () {
335 		Array sprites = new Array(regions.size);
336 		for (int i = 0, n = regions.size; i < n; i++)
337 			sprites.add(newSprite(regions.get(i)));
338 		return sprites;
339 	}
340 
341 	/** Returns the first region found with the specified name as a sprite. If whitespace was stripped from the region when it was
342 	 * packed, the sprite is automatically positioned as if whitespace had not been stripped. This method uses string comparison to
343 	 * find the region and constructs a new sprite, so the result should be cached rather than calling this method multiple times.
344 	 * @return The sprite, or null. */
createSprite(String name)345 	public Sprite createSprite (String name) {
346 		for (int i = 0, n = regions.size; i < n; i++)
347 			if (regions.get(i).name.equals(name)) return newSprite(regions.get(i));
348 		return null;
349 	}
350 
351 	/** Returns the first region found with the specified name and index as a sprite. This method uses string comparison to find the
352 	 * region and constructs a new sprite, so the result should be cached rather than calling this method multiple times.
353 	 * @return The sprite, or null.
354 	 * @see #createSprite(String) */
createSprite(String name, int index)355 	public Sprite createSprite (String name, int index) {
356 		for (int i = 0, n = regions.size; i < n; i++) {
357 			AtlasRegion region = regions.get(i);
358 			if (!region.name.equals(name)) continue;
359 			if (region.index != index) continue;
360 			return newSprite(regions.get(i));
361 		}
362 		return null;
363 	}
364 
365 	/** Returns all regions with the specified name as sprites, ordered by smallest to largest {@link AtlasRegion#index index}. This
366 	 * method uses string comparison to find the regions and constructs new sprites, so the result should be cached rather than
367 	 * calling this method multiple times.
368 	 * @see #createSprite(String) */
createSprites(String name)369 	public Array<Sprite> createSprites (String name) {
370 		Array<Sprite> matched = new Array();
371 		for (int i = 0, n = regions.size; i < n; i++) {
372 			AtlasRegion region = regions.get(i);
373 			if (region.name.equals(name)) matched.add(newSprite(region));
374 		}
375 		return matched;
376 	}
377 
newSprite(AtlasRegion region)378 	private Sprite newSprite (AtlasRegion region) {
379 		if (region.packedWidth == region.originalWidth && region.packedHeight == region.originalHeight) {
380 			if (region.rotate) {
381 				Sprite sprite = new Sprite(region);
382 				sprite.setBounds(0, 0, region.getRegionHeight(), region.getRegionWidth());
383 				sprite.rotate90(true);
384 				return sprite;
385 			}
386 			return new Sprite(region);
387 		}
388 		return new AtlasSprite(region);
389 	}
390 
391 	/** Returns the first region found with the specified name as a {@link NinePatch}. The region must have been packed with
392 	 * ninepatch splits. This method uses string comparison to find the region and constructs a new ninepatch, so the result should
393 	 * be cached rather than calling this method multiple times.
394 	 * @return The ninepatch, or null. */
createPatch(String name)395 	public NinePatch createPatch (String name) {
396 		for (int i = 0, n = regions.size; i < n; i++) {
397 			AtlasRegion region = regions.get(i);
398 			if (region.name.equals(name)) {
399 				int[] splits = region.splits;
400 				if (splits == null) throw new IllegalArgumentException("Region does not have ninepatch splits: " + name);
401 				NinePatch patch = new NinePatch(region, splits[0], splits[1], splits[2], splits[3]);
402 				if (region.pads != null) patch.setPadding(region.pads[0], region.pads[1], region.pads[2], region.pads[3]);
403 				return patch;
404 			}
405 		}
406 		return null;
407 	}
408 
409 	/** @return the textures of the pages, unordered */
getTextures()410 	public ObjectSet<Texture> getTextures () {
411 		return textures;
412 	}
413 
414 	/** Releases all resources associated with this TextureAtlas instance. This releases all the textures backing all TextureRegions
415 	 * and Sprites, which should no longer be used after calling dispose. */
dispose()416 	public void dispose () {
417 		for (Texture texture : textures)
418 			texture.dispose();
419 		textures.clear();
420 	}
421 
422 	static final Comparator<Region> indexComparator = new Comparator<Region>() {
423 		public int compare (Region region1, Region region2) {
424 			int i1 = region1.index;
425 			if (i1 == -1) i1 = Integer.MAX_VALUE;
426 			int i2 = region2.index;
427 			if (i2 == -1) i2 = Integer.MAX_VALUE;
428 			return i1 - i2;
429 		}
430 	};
431 
readValue(BufferedReader reader)432 	static String readValue (BufferedReader reader) throws IOException {
433 		String line = reader.readLine();
434 		int colon = line.indexOf(':');
435 		if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line);
436 		return line.substring(colon + 1).trim();
437 	}
438 
439 	/** Returns the number of tuple values read (1, 2 or 4). */
readTuple(BufferedReader reader)440 	static int readTuple (BufferedReader reader) throws IOException {
441 		String line = reader.readLine();
442 		int colon = line.indexOf(':');
443 		if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line);
444 		int i = 0, lastMatch = colon + 1;
445 		for (i = 0; i < 3; i++) {
446 			int comma = line.indexOf(',', lastMatch);
447 			if (comma == -1) break;
448 			tuple[i] = line.substring(lastMatch, comma).trim();
449 			lastMatch = comma + 1;
450 		}
451 		tuple[i] = line.substring(lastMatch).trim();
452 		return i + 1;
453 	}
454 
455 	/** Describes the region of a packed image and provides information about the original image before it was packed. */
456 	static public class AtlasRegion extends TextureRegion {
457 		/** The number at the end of the original image file name, or -1 if none.<br>
458 		 * <br>
459 		 * When sprites are packed, if the original file name ends with a number, it is stored as the index and is not considered as
460 		 * part of the sprite's name. This is useful for keeping animation frames in order.
461 		 * @see TextureAtlas#findRegions(String) */
462 		public int index;
463 
464 		/** The name of the original image file, up to the first underscore. Underscores denote special instructions to the texture
465 		 * packer. */
466 		public String name;
467 
468 		/** The offset from the left of the original image to the left of the packed image, after whitespace was removed for packing. */
469 		public float offsetX;
470 
471 		/** The offset from the bottom of the original image to the bottom of the packed image, after whitespace was removed for
472 		 * packing. */
473 		public float offsetY;
474 
475 		/** The width of the image, after whitespace was removed for packing. */
476 		public int packedWidth;
477 
478 		/** The height of the image, after whitespace was removed for packing. */
479 		public int packedHeight;
480 
481 		/** The width of the image, before whitespace was removed and rotation was applied for packing. */
482 		public int originalWidth;
483 
484 		/** The height of the image, before whitespace was removed for packing. */
485 		public int originalHeight;
486 
487 		/** If true, the region has been rotated 90 degrees counter clockwise. */
488 		public boolean rotate;
489 
490 		/** The ninepatch splits, or null if not a ninepatch. Has 4 elements: left, right, top, bottom. */
491 		public int[] splits;
492 
493 		/** The ninepatch pads, or null if not a ninepatch or the has no padding. Has 4 elements: left, right, top, bottom. */
494 		public int[] pads;
495 
AtlasRegion(Texture texture, int x, int y, int width, int height)496 		public AtlasRegion (Texture texture, int x, int y, int width, int height) {
497 			super(texture, x, y, width, height);
498 			originalWidth = width;
499 			originalHeight = height;
500 			packedWidth = width;
501 			packedHeight = height;
502 		}
503 
AtlasRegion(AtlasRegion region)504 		public AtlasRegion (AtlasRegion region) {
505 			setRegion(region);
506 			index = region.index;
507 			name = region.name;
508 			offsetX = region.offsetX;
509 			offsetY = region.offsetY;
510 			packedWidth = region.packedWidth;
511 			packedHeight = region.packedHeight;
512 			originalWidth = region.originalWidth;
513 			originalHeight = region.originalHeight;
514 			rotate = region.rotate;
515 			splits = region.splits;
516 		}
517 
518 		@Override
519 		/** Flips the region, adjusting the offset so the image appears to be flip as if no whitespace has been removed for packing. */
flip(boolean x, boolean y)520 		public void flip (boolean x, boolean y) {
521 			super.flip(x, y);
522 			if (x) offsetX = originalWidth - offsetX - getRotatedPackedWidth();
523 			if (y) offsetY = originalHeight - offsetY - getRotatedPackedHeight();
524 		}
525 
526 		/** Returns the packed width considering the rotate value, if it is true then it returns the packedHeight, otherwise it
527 		 * returns the packedWidth. */
getRotatedPackedWidth()528 		public float getRotatedPackedWidth () {
529 			return rotate ? packedHeight : packedWidth;
530 		}
531 
532 		/** Returns the packed height considering the rotate value, if it is true then it returns the packedWidth, otherwise it
533 		 * returns the packedHeight. */
getRotatedPackedHeight()534 		public float getRotatedPackedHeight () {
535 			return rotate ? packedWidth : packedHeight;
536 		}
537 
toString()538 		public String toString () {
539 			return name;
540 		}
541 	}
542 
543 	/** A sprite that, if whitespace was stripped from the region when it was packed, is automatically positioned as if whitespace
544 	 * had not been stripped. */
545 	static public class AtlasSprite extends Sprite {
546 		final AtlasRegion region;
547 		float originalOffsetX, originalOffsetY;
548 
AtlasSprite(AtlasRegion region)549 		public AtlasSprite (AtlasRegion region) {
550 			this.region = new AtlasRegion(region);
551 			originalOffsetX = region.offsetX;
552 			originalOffsetY = region.offsetY;
553 			setRegion(region);
554 			setOrigin(region.originalWidth / 2f, region.originalHeight / 2f);
555 			int width = region.getRegionWidth();
556 			int height = region.getRegionHeight();
557 			if (region.rotate) {
558 				super.rotate90(true);
559 				super.setBounds(region.offsetX, region.offsetY, height, width);
560 			} else
561 				super.setBounds(region.offsetX, region.offsetY, width, height);
562 			setColor(1, 1, 1, 1);
563 		}
564 
AtlasSprite(AtlasSprite sprite)565 		public AtlasSprite (AtlasSprite sprite) {
566 			region = sprite.region;
567 			this.originalOffsetX = sprite.originalOffsetX;
568 			this.originalOffsetY = sprite.originalOffsetY;
569 			set(sprite);
570 		}
571 
572 		@Override
setPosition(float x, float y)573 		public void setPosition (float x, float y) {
574 			super.setPosition(x + region.offsetX, y + region.offsetY);
575 		}
576 
577 		@Override
setX(float x)578 		public void setX (float x) {
579 			super.setX(x + region.offsetX);
580 		}
581 
582 		@Override
setY(float y)583 		public void setY (float y) {
584 			super.setY(y + region.offsetY);
585 		}
586 
587 		@Override
setBounds(float x, float y, float width, float height)588 		public void setBounds (float x, float y, float width, float height) {
589 			float widthRatio = width / region.originalWidth;
590 			float heightRatio = height / region.originalHeight;
591 			region.offsetX = originalOffsetX * widthRatio;
592 			region.offsetY = originalOffsetY * heightRatio;
593 			int packedWidth = region.rotate ? region.packedHeight : region.packedWidth;
594 			int packedHeight = region.rotate ? region.packedWidth : region.packedHeight;
595 			super.setBounds(x + region.offsetX, y + region.offsetY, packedWidth * widthRatio, packedHeight * heightRatio);
596 		}
597 
598 		@Override
setSize(float width, float height)599 		public void setSize (float width, float height) {
600 			setBounds(getX(), getY(), width, height);
601 		}
602 
603 		@Override
setOrigin(float originX, float originY)604 		public void setOrigin (float originX, float originY) {
605 			super.setOrigin(originX - region.offsetX, originY - region.offsetY);
606 		}
607 
608 		@Override
setOriginCenter()609 		public void setOriginCenter () {
610 			super.setOrigin(width / 2 - region.offsetX, height / 2 - region.offsetY);
611 		}
612 
613 		@Override
flip(boolean x, boolean y)614 		public void flip (boolean x, boolean y) {
615 			// Flip texture.
616 			if (region.rotate)
617 				super.flip(y, x);
618 			else
619 				super.flip(x, y);
620 
621 			float oldOriginX = getOriginX();
622 			float oldOriginY = getOriginY();
623 			float oldOffsetX = region.offsetX;
624 			float oldOffsetY = region.offsetY;
625 
626 			float widthRatio = getWidthRatio();
627 			float heightRatio = getHeightRatio();
628 
629 			region.offsetX = originalOffsetX;
630 			region.offsetY = originalOffsetY;
631 			region.flip(x, y); // Updates x and y offsets.
632 			originalOffsetX = region.offsetX;
633 			originalOffsetY = region.offsetY;
634 			region.offsetX *= widthRatio;
635 			region.offsetY *= heightRatio;
636 
637 			// Update position and origin with new offsets.
638 			translate(region.offsetX - oldOffsetX, region.offsetY - oldOffsetY);
639 			setOrigin(oldOriginX, oldOriginY);
640 		}
641 
642 		@Override
rotate90(boolean clockwise)643 		public void rotate90 (boolean clockwise) {
644 			// Rotate texture.
645 			super.rotate90(clockwise);
646 
647 			float oldOriginX = getOriginX();
648 			float oldOriginY = getOriginY();
649 			float oldOffsetX = region.offsetX;
650 			float oldOffsetY = region.offsetY;
651 
652 			float widthRatio = getWidthRatio();
653 			float heightRatio = getHeightRatio();
654 
655 			if (clockwise) {
656 				region.offsetX = oldOffsetY;
657 				region.offsetY = region.originalHeight * heightRatio - oldOffsetX - region.packedWidth * widthRatio;
658 			} else {
659 				region.offsetX = region.originalWidth * widthRatio - oldOffsetY - region.packedHeight * heightRatio;
660 				region.offsetY = oldOffsetX;
661 			}
662 
663 			// Update position and origin with new offsets.
664 			translate(region.offsetX - oldOffsetX, region.offsetY - oldOffsetY);
665 			setOrigin(oldOriginX, oldOriginY);
666 		}
667 
668 		@Override
getX()669 		public float getX () {
670 			return super.getX() - region.offsetX;
671 		}
672 
673 		@Override
getY()674 		public float getY () {
675 			return super.getY() - region.offsetY;
676 		}
677 
678 		@Override
getOriginX()679 		public float getOriginX () {
680 			return super.getOriginX() + region.offsetX;
681 		}
682 
683 		@Override
getOriginY()684 		public float getOriginY () {
685 			return super.getOriginY() + region.offsetY;
686 		}
687 
688 		@Override
getWidth()689 		public float getWidth () {
690 			return super.getWidth() / region.getRotatedPackedWidth() * region.originalWidth;
691 		}
692 
693 		@Override
getHeight()694 		public float getHeight () {
695 			return super.getHeight() / region.getRotatedPackedHeight() * region.originalHeight;
696 		}
697 
getWidthRatio()698 		public float getWidthRatio () {
699 			return super.getWidth() / region.getRotatedPackedWidth();
700 		}
701 
getHeightRatio()702 		public float getHeightRatio () {
703 			return super.getHeight() / region.getRotatedPackedHeight();
704 		}
705 
getAtlasRegion()706 		public AtlasRegion getAtlasRegion () {
707 			return region;
708 		}
709 
toString()710 		public String toString () {
711 			return region.toString();
712 		}
713 	}
714 }
715