• 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 java.util.Comparator;
20 
21 import com.badlogic.gdx.Gdx;
22 import com.badlogic.gdx.graphics.Color;
23 import com.badlogic.gdx.graphics.Pixmap;
24 import com.badlogic.gdx.graphics.Pixmap.Blending;
25 import com.badlogic.gdx.graphics.Pixmap.Format;
26 import com.badlogic.gdx.graphics.Texture;
27 import com.badlogic.gdx.graphics.Texture.TextureFilter;
28 import com.badlogic.gdx.graphics.g2d.PixmapPacker.SkylineStrategy.SkylinePage.Row;
29 import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
30 import com.badlogic.gdx.math.Rectangle;
31 import com.badlogic.gdx.utils.Array;
32 import com.badlogic.gdx.utils.Disposable;
33 import com.badlogic.gdx.utils.GdxRuntimeException;
34 import com.badlogic.gdx.utils.OrderedMap;
35 
36 /** Packs {@link Pixmap pixmaps} into one or more {@link Page pages} to generate an atlas of pixmap instances. Provides means to
37  * directly convert the pixmap atlas to a {@link TextureAtlas}. The packer supports padding and border pixel duplication,
38  * specified during construction. The packer supports incremental inserts and updates of TextureAtlases generated with this class.
39  * How bin packing is performed can be customized via {@link PackStrategy}.
40  * <p>
41  * All methods can be called from any thread unless otherwise noted.
42  * <p>
43  * One-off usage:
44  *
45  * <pre>
46  * // 512x512 pixel pages, RGB565 format, 2 pixels of padding, border duplication
47  * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, true);
48  * packer.pack(&quot;First Pixmap&quot;, pixmap1);
49  * packer.pack(&quot;Second Pixmap&quot;, pixmap2);
50  * TextureAtlas atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false);
51  * packer.dispose();
52  * // ...
53  * atlas.dispose();
54  * </pre>
55  *
56  * With this usage pattern, disposing the packer will not dispose any pixmaps used by the texture atlas. The texture atlas must
57  * also be disposed when no longer needed.
58  *
59  * Incremental texture atlas usage:
60  *
61  * <pre>
62  * // 512x512 pixel pages, RGB565 format, 2 pixels of padding, no border duplication
63  * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, false);
64  * TextureAtlas atlas = new TextureAtlas();
65  *
66  * // potentially on a separate thread, e.g. downloading thumbnails
67  * packer.pack(&quot;thumbnail&quot;, thumbnail);
68  *
69  * // on the rendering thread, every frame
70  * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
71  *
72  * // once the atlas is no longer needed, make sure you get the final additions. This might
73  * // be more elaborate depending on your threading model.
74  * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
75  * // ...
76  * atlas.dispose();
77  * </pre>
78  *
79  * Pixmap-only usage:
80  *
81  * <pre>
82  * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, true);
83  * packer.pack(&quot;First Pixmap&quot;, pixmap1);
84  * packer.pack(&quot;Second Pixmap&quot;, pixmap2);
85  *
86  * // do something interesting with the resulting pages
87  * for (Page page : packer.getPages()) {
88  * 	// ...
89  * }
90  *
91  * packer.dispose();
92  * </pre>
93  *
94  * @author mzechner
95  * @author Nathan Sweet
96  * @author Rob Rendell */
97 public class PixmapPacker implements Disposable {
98 	boolean packToTexture;
99 	boolean disposed;
100 	int pageWidth, pageHeight;
101 	Format pageFormat;
102 	int padding;
103 	boolean duplicateBorder;
104 	Color transparentColor = new Color(0f, 0f, 0f, 0f);
105 	final Array<Page> pages = new Array();
106 	PackStrategy packStrategy;
107 
108 	/** Uses {@link GuillotineStrategy}.
109 	 * @see PixmapPacker#PixmapPacker(int, int, Format, int, boolean, PackStrategy) */
PixmapPacker(int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder)110 	public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder) {
111 		this(pageWidth, pageHeight, pageFormat, padding, duplicateBorder, new GuillotineStrategy());
112 	}
113 
114 	/** Creates a new ImagePacker which will insert all supplied pixmaps into one or more <code>pageWidth</code> by
115 	 * <code>pageHeight</code> pixmaps using the specified strategy.
116 	 * @param padding the number of blank pixels to insert between pixmaps.
117 	 * @param duplicateBorder duplicate the border pixels of the inserted images to avoid seams when rendering with bi-linear
118 	 *           filtering on. */
PixmapPacker(int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder, PackStrategy packStrategy)119 	public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder,
120 		PackStrategy packStrategy) {
121 		this.pageWidth = pageWidth;
122 		this.pageHeight = pageHeight;
123 		this.pageFormat = pageFormat;
124 		this.padding = padding;
125 		this.duplicateBorder = duplicateBorder;
126 		this.packStrategy = packStrategy;
127 	}
128 
129 	/** Sorts the images to the optimzal order they should be packed. Some packing strategies rely heavily on the images being
130 	 * sorted. */
sort(Array<Pixmap> images)131 	public void sort (Array<Pixmap> images) {
132 		packStrategy.sort(images);
133 	}
134 
135 	/** Inserts the pixmap without a name. It cannot be looked up by name.
136 	 * @see #pack(String, Pixmap) */
pack(Pixmap image)137 	public synchronized Rectangle pack (Pixmap image) {
138 		return pack(null, image);
139 	}
140 
141 	/** Inserts the pixmap. If name was not null, you can later retrieve the image's position in the output image via
142 	 * {@link #getRect(String)}.
143 	 * @param name If null, the image cannot be looked up by name.
144 	 * @return Rectangle describing the area the pixmap was rendered to.
145 	 * @throws GdxRuntimeException in case the image did not fit due to the page size being too small or providing a duplicate
146 	 *            name. */
pack(String name, Pixmap image)147 	public synchronized Rectangle pack (String name, Pixmap image) {
148 		if (disposed) return null;
149 		if (name != null && getRect(name) != null)
150 			throw new GdxRuntimeException("Pixmap has already been packed with name: " + name);
151 
152 		Rectangle rect = new Rectangle(0, 0, image.getWidth(), image.getHeight());
153 		if (rect.getWidth() > pageWidth || rect.getHeight() > pageHeight) {
154 			if (name == null) throw new GdxRuntimeException("Page size too small for pixmap.");
155 			throw new GdxRuntimeException("Page size too small for pixmap: " + name);
156 		}
157 
158 		Page page = packStrategy.pack(this, name, rect);
159 		if (name != null) {
160 			page.rects.put(name, rect);
161 			page.addedRects.add(name);
162 		}
163 
164 		int rectX = (int)rect.x, rectY = (int)rect.y, rectWidth = (int)rect.width, rectHeight = (int)rect.height;
165 
166 		if (packToTexture && !duplicateBorder && page.texture != null && !page.dirty) {
167 			page.texture.bind();
168 			Gdx.gl.glTexSubImage2D(page.texture.glTarget, 0, rectX, rectY, rectWidth, rectHeight, image.getGLFormat(),
169 				image.getGLType(), image.getPixels());
170 		} else
171 			page.dirty = true;
172 
173 		Blending blending = Pixmap.getBlending();
174 		Pixmap.setBlending(Blending.None);
175 
176 		page.image.drawPixmap(image, rectX, rectY);
177 
178 		if (duplicateBorder) {
179 			int imageWidth = image.getWidth(), imageHeight = image.getHeight();
180 			// Copy corner pixels to fill corners of the padding.
181 			page.image.drawPixmap(image, 0, 0, 1, 1, rectX - 1, rectY - 1, 1, 1);
182 			page.image.drawPixmap(image, imageWidth - 1, 0, 1, 1, rectX + rectWidth, rectY - 1, 1, 1);
183 			page.image.drawPixmap(image, 0, imageHeight - 1, 1, 1, rectX - 1, rectY + rectHeight, 1, 1);
184 			page.image.drawPixmap(image, imageWidth - 1, imageHeight - 1, 1, 1, rectX + rectWidth, rectY + rectHeight, 1, 1);
185 			// Copy edge pixels into padding.
186 			page.image.drawPixmap(image, 0, 0, imageWidth, 1, rectX, rectY - 1, rectWidth, 1);
187 			page.image.drawPixmap(image, 0, imageHeight - 1, imageWidth, 1, rectX, rectY + rectHeight, rectWidth, 1);
188 			page.image.drawPixmap(image, 0, 0, 1, imageHeight, rectX - 1, rectY, 1, rectHeight);
189 			page.image.drawPixmap(image, imageWidth - 1, 0, 1, imageHeight, rectX + rectWidth, rectY, 1, rectHeight);
190 		}
191 
192 		Pixmap.setBlending(blending);
193 
194 		return rect;
195 	}
196 
197 	/** @return the {@link Page} instances created so far. If multiple threads are accessing the packer, iterating over the pages
198 	 *         must be done only after synchronizing on the packer. */
getPages()199 	public Array<Page> getPages () {
200 		return pages;
201 	}
202 
203 	/** @param name the name of the image
204 	 * @return the rectangle for the image in the page it's stored in or null */
getRect(String name)205 	public synchronized Rectangle getRect (String name) {
206 		for (Page page : pages) {
207 			Rectangle rect = page.rects.get(name);
208 			if (rect != null) return rect;
209 		}
210 		return null;
211 	}
212 
213 	/** @param name the name of the image
214 	 * @return the page the image is stored in or null */
getPage(String name)215 	public synchronized Page getPage (String name) {
216 		for (Page page : pages) {
217 			Rectangle rect = page.rects.get(name);
218 			if (rect != null) return page;
219 		}
220 		return null;
221 	}
222 
223 	/** Returns the index of the page containing the given packed rectangle.
224 	 * @param name the name of the image
225 	 * @return the index of the page the image is stored in or -1 */
getPageIndex(String name)226 	public synchronized int getPageIndex (String name) {
227 		for (int i = 0; i < pages.size; i++) {
228 			Rectangle rect = pages.get(i).rects.get(name);
229 			if (rect != null) return i;
230 		}
231 		return -1;
232 	}
233 
234 	/** Disposes any pixmap pages which don't have a texture. Page pixmaps that have a texture will not be disposed until their
235 	 * texture is disposed. */
dispose()236 	public synchronized void dispose () {
237 		for (Page page : pages) {
238 			if (page.texture == null) {
239 				page.image.dispose();
240 			}
241 		}
242 		disposed = true;
243 	}
244 
245 	/** Generates a new {@link TextureAtlas} from the pixmaps inserted so far. After calling this method, disposing the packer will
246 	 * no longer dispose the page pixmaps. */
generateTextureAtlas(TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps)247 	public synchronized TextureAtlas generateTextureAtlas (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
248 		TextureAtlas atlas = new TextureAtlas();
249 		updateTextureAtlas(atlas, minFilter, magFilter, useMipMaps);
250 		return atlas;
251 	}
252 
253 	/** Updates the {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method. This
254 	 * can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the TextureAtlas on
255 	 * the rendering thread. This method must be called on the rendering thread. After calling this method, disposing the packer
256 	 * will no longer dispose the page pixmaps. */
updateTextureAtlas(TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps)257 	public synchronized void updateTextureAtlas (TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter,
258 		boolean useMipMaps) {
259 		updatePageTextures(minFilter, magFilter, useMipMaps);
260 		for (Page page : pages) {
261 			if (page.addedRects.size > 0) {
262 				for (String name : page.addedRects) {
263 					Rectangle rect = page.rects.get(name);
264 					TextureRegion region = new TextureRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width,
265 						(int)rect.height);
266 					atlas.addRegion(name, region);
267 				}
268 				page.addedRects.clear();
269 				atlas.getTextures().add(page.texture);
270 			}
271 		}
272 	}
273 
274 	/** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page and adds a region to
275 	 * the specified array for each page texture. */
updateTextureRegions(Array<TextureRegion> regions, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps)276 	public synchronized void updateTextureRegions (Array<TextureRegion> regions, TextureFilter minFilter, TextureFilter magFilter,
277 		boolean useMipMaps) {
278 		updatePageTextures(minFilter, magFilter, useMipMaps);
279 		while (regions.size < pages.size)
280 			regions.add(new TextureRegion(pages.get(regions.size).texture));
281 	}
282 
283 	/** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page. */
updatePageTextures(TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps)284 	public synchronized void updatePageTextures (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
285 		for (Page page : pages)
286 			page.updateTexture(minFilter, magFilter, useMipMaps);
287 	}
288 
getPageWidth()289 	public int getPageWidth () {
290 		return pageWidth;
291 	}
292 
setPageWidth(int pageWidth)293 	public void setPageWidth (int pageWidth) {
294 		this.pageWidth = pageWidth;
295 	}
296 
getPageHeight()297 	public int getPageHeight () {
298 		return pageHeight;
299 	}
300 
setPageHeight(int pageHeight)301 	public void setPageHeight (int pageHeight) {
302 		this.pageHeight = pageHeight;
303 	}
304 
getPageFormat()305 	public Format getPageFormat () {
306 		return pageFormat;
307 	}
308 
setPageFormat(Format pageFormat)309 	public void setPageFormat (Format pageFormat) {
310 		this.pageFormat = pageFormat;
311 	}
312 
getPadding()313 	public int getPadding () {
314 		return padding;
315 	}
316 
setPadding(int padding)317 	public void setPadding (int padding) {
318 		this.padding = padding;
319 	}
320 
getDuplicateBorder()321 	public boolean getDuplicateBorder () {
322 		return duplicateBorder;
323 	}
324 
setDuplicateBorder(boolean duplicateBorder)325 	public void setDuplicateBorder (boolean duplicateBorder) {
326 		this.duplicateBorder = duplicateBorder;
327 	}
328 
getPackToTexture()329 	public boolean getPackToTexture () {
330 		return packToTexture;
331 	}
332 
333 	/** If true, when a pixmap is packed to a page that has a texture, the portion of the texture where the pixmap was packed is
334 	 * updated using glTexSubImage2D. Note if packing many pixmaps, this may be slower than reuploading the whole texture. This
335 	 * setting is ignored if {@link #getDuplicateBorder()} is true. */
setPackToTexture(boolean packToTexture)336 	public void setPackToTexture (boolean packToTexture) {
337 		this.packToTexture = packToTexture;
338 	}
339 
340 	/** @author mzechner
341 	 * @author Nathan Sweet
342 	 * @author Rob Rendell */
343 	static public class Page {
344 		OrderedMap<String, Rectangle> rects = new OrderedMap();
345 		Pixmap image;
346 		Texture texture;
347 		final Array<String> addedRects = new Array();
348 		boolean dirty;
349 
350 		/** Creates a new page filled with the color provided by the {@link PixmapPacker#getTransparentColor()} */
Page(PixmapPacker packer)351 		public Page (PixmapPacker packer) {
352 			image = new Pixmap(packer.pageWidth, packer.pageHeight, packer.pageFormat);
353 			final Color transparentColor = packer.getTransparentColor();
354 			this.image.setColor(transparentColor);
355 			this.image.fill();
356 		}
357 
getPixmap()358 		public Pixmap getPixmap () {
359 			return image;
360 		}
361 
getRects()362 		public OrderedMap<String, Rectangle> getRects () {
363 			return rects;
364 		}
365 
366 		/** Returns the texture for this page, or null if the texture has not been created.
367 		 * @see #updateTexture(TextureFilter, TextureFilter, boolean) */
getTexture()368 		public Texture getTexture () {
369 			return texture;
370 		}
371 
372 		/** Creates the texture if it has not been created, else reuploads the entire page pixmap to the texture if the pixmap has
373 		 * changed since this method was last called.
374 		 * @return true if the texture was created or reuploaded. */
updateTexture(TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps)375 		public boolean updateTexture (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
376 			if (texture != null) {
377 				if (!dirty) return false;
378 				texture.load(texture.getTextureData());
379 			} else {
380 				texture = new Texture(new PixmapTextureData(image, image.getFormat(), useMipMaps, false, true)) {
381 					@Override
382 					public void dispose () {
383 						super.dispose();
384 						image.dispose();
385 					}
386 				};
387 				texture.setFilter(minFilter, magFilter);
388 			}
389 			dirty = false;
390 			return true;
391 		}
392 	}
393 
394 	/** Choose the page and location for each rectangle.
395 	 * @author Nathan Sweet */
396 	static public interface PackStrategy {
sort(Array<Pixmap> images)397 		public void sort (Array<Pixmap> images);
398 
399 		/** Returns the page the rectangle should be placed in and modifies the specified rectangle position. */
pack(PixmapPacker packer, String name, Rectangle rect)400 		public Page pack (PixmapPacker packer, String name, Rectangle rect);
401 	}
402 
403 	/** Does bin packing by inserting to the right or below previously packed rectangles. This is good at packing arbitrarily sized
404 	 * images.
405 	 * @author mzechner
406 	 * @author Nathan Sweet
407 	 * @author Rob Rendell */
408 	static public class GuillotineStrategy implements PackStrategy {
409 		Comparator<Pixmap> comparator;
410 
sort(Array<Pixmap> pixmaps)411 		public void sort (Array<Pixmap> pixmaps) {
412 			if (comparator == null) {
413 				comparator = new Comparator<Pixmap>() {
414 					public int compare (Pixmap o1, Pixmap o2) {
415 						return Math.max(o1.getWidth(), o1.getHeight()) - Math.max(o2.getWidth(), o2.getHeight());
416 					}
417 				};
418 			}
419 			pixmaps.sort(comparator);
420 		}
421 
pack(PixmapPacker packer, String name, Rectangle rect)422 		public Page pack (PixmapPacker packer, String name, Rectangle rect) {
423 			GuillotinePage page;
424 			if (packer.pages.size == 0) {
425 				// Add a page if empty.
426 				page = new GuillotinePage(packer);
427 				packer.pages.add(page);
428 			} else {
429 				// Always try to pack into the last page.
430 				page = (GuillotinePage)packer.pages.peek();
431 			}
432 
433 			int padding = packer.padding;
434 			rect.width += padding;
435 			rect.height += padding;
436 			Node node = insert(page.root, rect);
437 			if (node == null) {
438 				// Didn't fit, pack into a new page.
439 				page = new GuillotinePage(packer);
440 				packer.pages.add(page);
441 				node = insert(page.root, rect);
442 			}
443 			node.full = true;
444 			rect.set(node.rect.x, node.rect.y, node.rect.width - padding, node.rect.height - padding);
445 			return page;
446 		}
447 
insert(Node node, Rectangle rect)448 		private Node insert (Node node, Rectangle rect) {
449 			if (!node.full && node.leftChild != null && node.rightChild != null) {
450 				Node newNode = insert(node.leftChild, rect);
451 				if (newNode == null) newNode = insert(node.rightChild, rect);
452 				return newNode;
453 			} else {
454 				if (node.full) return null;
455 				if (node.rect.width == rect.width && node.rect.height == rect.height) return node;
456 				if (node.rect.width < rect.width || node.rect.height < rect.height) return null;
457 
458 				node.leftChild = new Node();
459 				node.rightChild = new Node();
460 
461 				int deltaWidth = (int)node.rect.width - (int)rect.width;
462 				int deltaHeight = (int)node.rect.height - (int)rect.height;
463 				if (deltaWidth > deltaHeight) {
464 					node.leftChild.rect.x = node.rect.x;
465 					node.leftChild.rect.y = node.rect.y;
466 					node.leftChild.rect.width = rect.width;
467 					node.leftChild.rect.height = node.rect.height;
468 
469 					node.rightChild.rect.x = node.rect.x + rect.width;
470 					node.rightChild.rect.y = node.rect.y;
471 					node.rightChild.rect.width = node.rect.width - rect.width;
472 					node.rightChild.rect.height = node.rect.height;
473 				} else {
474 					node.leftChild.rect.x = node.rect.x;
475 					node.leftChild.rect.y = node.rect.y;
476 					node.leftChild.rect.width = node.rect.width;
477 					node.leftChild.rect.height = rect.height;
478 
479 					node.rightChild.rect.x = node.rect.x;
480 					node.rightChild.rect.y = node.rect.y + rect.height;
481 					node.rightChild.rect.width = node.rect.width;
482 					node.rightChild.rect.height = node.rect.height - rect.height;
483 				}
484 
485 				return insert(node.leftChild, rect);
486 			}
487 		}
488 
489 		static final class Node {
490 			public Node leftChild;
491 			public Node rightChild;
492 			public final Rectangle rect = new Rectangle();
493 			public boolean full;
494 		}
495 
496 		static class GuillotinePage extends Page {
497 			Node root;
498 
GuillotinePage(PixmapPacker packer)499 			public GuillotinePage (PixmapPacker packer) {
500 				super(packer);
501 				root = new Node();
502 				root.rect.x = packer.padding;
503 				root.rect.y = packer.padding;
504 				root.rect.width = packer.pageWidth - packer.padding * 2;
505 				root.rect.height = packer.pageHeight - packer.padding * 2;
506 			}
507 		}
508 	}
509 
510 	/** Does bin packing by inserting in rows. This is good at packing images that have similar heights.
511 	 * @author Nathan Sweet */
512 	static public class SkylineStrategy implements PackStrategy {
513 		Comparator<Pixmap> comparator;
514 
sort(Array<Pixmap> images)515 		public void sort (Array<Pixmap> images) {
516 			if (comparator == null) {
517 				comparator = new Comparator<Pixmap>() {
518 					public int compare (Pixmap o1, Pixmap o2) {
519 						return o1.getHeight() - o2.getHeight();
520 					}
521 				};
522 			}
523 			images.sort(comparator);
524 		}
525 
pack(PixmapPacker packer, String name, Rectangle rect)526 		public Page pack (PixmapPacker packer, String name, Rectangle rect) {
527 			int padding = packer.padding;
528 			int pageWidth = packer.pageWidth - padding * 2, pageHeight = packer.pageHeight - padding * 2;
529 			int rectWidth = (int)rect.width + padding, rectHeight = (int)rect.height + padding;
530 			for (int i = 0, n = packer.pages.size; i < n; i++) {
531 				SkylinePage page = (SkylinePage)packer.pages.get(i);
532 				Row bestRow = null;
533 				// Fit in any row before the last.
534 				for (int ii = 0, nn = page.rows.size - 1; ii < nn; ii++) {
535 					Row row = page.rows.get(ii);
536 					if (row.x + rectWidth >= pageWidth) continue;
537 					if (row.y + rectHeight >= pageHeight) continue;
538 					if (rectHeight > row.height) continue;
539 					if (bestRow == null || row.height < bestRow.height) bestRow = row;
540 				}
541 				if (bestRow == null) {
542 					// Fit in last row, increasing height.
543 					Row row = page.rows.peek();
544 					if (row.y + rectHeight >= pageHeight) continue;
545 					if (row.x + rectWidth < pageWidth) {
546 						row.height = Math.max(row.height, rectHeight);
547 						bestRow = row;
548 					} else {
549 						// Fit in new row.
550 						bestRow = new Row();
551 						bestRow.y = row.y + row.height;
552 						bestRow.height = rectHeight;
553 						page.rows.add(bestRow);
554 					}
555 				}
556 				if (bestRow != null) {
557 					rect.x = bestRow.x;
558 					rect.y = bestRow.y;
559 					bestRow.x += rectWidth;
560 					return page;
561 				}
562 			}
563 			// Fit in new page.
564 			SkylinePage page = new SkylinePage(packer);
565 			packer.pages.add(page);
566 			Row row = new Row();
567 			row.x = padding + rectWidth;
568 			row.y = padding;
569 			row.height = rectHeight;
570 			page.rows.add(row);
571 			rect.x = padding;
572 			rect.y = padding;
573 			return page;
574 		}
575 
576 		static class SkylinePage extends Page {
577 			Array<Row> rows = new Array();
578 
SkylinePage(PixmapPacker packer)579 			public SkylinePage (PixmapPacker packer) {
580 				super(packer);
581 
582 			}
583 
584 			static class Row {
585 				int x, y, height;
586 			}
587 		}
588 	}
589 
590 	/** @see PixmapPacker#setTransparentColor(Color color) */
getTransparentColor()591 	public Color getTransparentColor () {
592 		return this.transparentColor;
593 	}
594 
595 	/** Sets the default <code>color</code> of the whole {@link PixmapPacker.Page} when a new one created. Helps to avoid texture
596 	 * bleeding or to highlight the page for debugging.
597 	 * @see Page#Page(PixmapPacker packer) */
setTransparentColor(Color color)598 	public void setTransparentColor (Color color) {
599 		this.transparentColor.set(color);
600 	}
601 
602 }
603