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("First Pixmap", pixmap1); 49 * packer.pack("Second Pixmap", 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("thumbnail", 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("First Pixmap", pixmap1); 84 * packer.pack("Second Pixmap", 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