All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gdx.graphics.g2d.PixmapPacker Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * 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 java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Blending;
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.g2d.PixmapPacker.SkylineStrategy.SkylinePage.Row;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.OrderedMap;

/** Packs {@link Pixmap pixmaps} into one or more {@link Page pages} to generate an atlas of pixmap instances. Provides means to
 * directly convert the pixmap atlas to a {@link TextureAtlas}. The packer supports padding and border pixel duplication,
 * specified during construction. The packer supports incremental inserts and updates of TextureAtlases generated with this class.
 * How bin packing is performed can be customized via {@link PackStrategy}.
 * 

* All methods can be called from any thread unless otherwise noted. *

* One-off usage: * *

 * // 512x512 pixel pages, RGB565 format, 2 pixels of padding, border duplication
 * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, true);
 * packer.pack("First Pixmap", pixmap1);
 * packer.pack("Second Pixmap", pixmap2);
 * TextureAtlas atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false);
 * packer.dispose();
 * // ...
 * atlas.dispose();
 * 
* * With this usage pattern, disposing the packer will not dispose any pixmaps used by the texture atlas. The texture atlas must * also be disposed when no longer needed. * * Incremental texture atlas usage: * *
 * // 512x512 pixel pages, RGB565 format, 2 pixels of padding, no border duplication
 * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, false);
 * TextureAtlas atlas = new TextureAtlas();
 * 
 * // potentially on a separate thread, e.g. downloading thumbnails
 * packer.pack("thumbnail", thumbnail);
 * 
 * // on the rendering thread, every frame
 * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
 * 
 * // once the atlas is no longer needed, make sure you get the final additions. This might
 * // be more elaborate depending on your threading model.
 * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
 * // ...
 * atlas.dispose();
 * 
* * Pixmap-only usage: * *
 * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, true);
 * packer.pack("First Pixmap", pixmap1);
 * packer.pack("Second Pixmap", pixmap2);
 * 
 * // do something interesting with the resulting pages
 * for (Page page : packer.getPages()) {
 * 	// ...
 * }
 * 
 * packer.dispose();
 * 
* * @author mzechner * @author Nathan Sweet * @author Rob Rendell */ public class PixmapPacker implements Disposable { boolean packToTexture; boolean disposed; int pageWidth, pageHeight; Format pageFormat; int padding; boolean duplicateBorder; boolean stripWhitespaceX, stripWhitespaceY; int alphaThreshold; Color transparentColor = new Color(0f, 0f, 0f, 0f); final Array pages = new Array(); PackStrategy packStrategy; static Pattern indexPattern = Pattern.compile("(.+)_(\\d+)$"); /** Uses {@link GuillotineStrategy}. * @see PixmapPacker#PixmapPacker(int, int, Format, int, boolean, boolean, boolean, PackStrategy) */ public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder) { this(pageWidth, pageHeight, pageFormat, padding, duplicateBorder, false, false, new GuillotineStrategy()); } /** Uses {@link GuillotineStrategy}. * @see PixmapPacker#PixmapPacker(int, int, Format, int, boolean, boolean, boolean, PackStrategy) */ public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder, PackStrategy packStrategy) { this(pageWidth, pageHeight, pageFormat, padding, duplicateBorder, false, false, packStrategy); } /** Creates a new PixmapPacker which will insert all supplied pixmaps into one or more pageWidth by * pageHeight pixmaps using the specified strategy. * @param padding the number of blank pixels to insert between pixmaps. * @param duplicateBorder duplicate the border pixels of the inserted images to avoid seams when rendering with bi-linear * filtering on. * @param stripWhitespaceX strip whitespace in x axis * @param stripWhitespaceY strip whitespace in y axis */ public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder, boolean stripWhitespaceX, boolean stripWhitespaceY, PackStrategy packStrategy) { this.pageWidth = pageWidth; this.pageHeight = pageHeight; this.pageFormat = pageFormat; this.padding = padding; this.duplicateBorder = duplicateBorder; this.stripWhitespaceX = stripWhitespaceX; this.stripWhitespaceY = stripWhitespaceY; this.packStrategy = packStrategy; } /** Sorts the images to the optimzal order they should be packed. Some packing strategies rely heavily on the images being * sorted. */ public void sort (Array images) { packStrategy.sort(images); } /** Inserts the pixmap without a name. It cannot be looked up by name. * @see #pack(String, Pixmap) */ public synchronized PixmapPackerRectangle pack (Pixmap image) { return pack(null, image); } /** Inserts the pixmap. If name was not null, you can later retrieve the image's position in the output image via * {@link #getRect(String)}. * @param name If null, the image cannot be looked up by name. * @return Rectangle describing the area the pixmap was rendered to. * @throws GdxRuntimeException in case the image did not fit due to the page size being too small or providing a duplicate * name. */ public synchronized PixmapPackerRectangle pack (String name, Pixmap image) { if (disposed) return null; if (name != null && getRect(name) != null) throw new GdxRuntimeException("Pixmap has already been packed with name: " + name); PixmapPackerRectangle rect; Pixmap pixmapToDispose = null; if (name != null && name.endsWith(".9")) { rect = new PixmapPackerRectangle(0, 0, image.getWidth() - 2, image.getHeight() - 2); pixmapToDispose = new Pixmap(image.getWidth() - 2, image.getHeight() - 2, image.getFormat()); pixmapToDispose.setBlending(Blending.None); rect.splits = getSplits(image); rect.pads = getPads(image, rect.splits); pixmapToDispose.drawPixmap(image, 0, 0, 1, 1, image.getWidth() - 1, image.getHeight() - 1); image = pixmapToDispose; name = name.split("\\.")[0]; } else { if (stripWhitespaceX || stripWhitespaceY) { int originalWidth = image.getWidth(); int originalHeight = image.getHeight(); // Strip whitespace, manipulate the pixmap and return corrected Rect int top = 0; int bottom = image.getHeight(); if (stripWhitespaceY) { outer: for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { int pixel = image.getPixel(x, y); int alpha = ((pixel & 0x000000ff)); if (alpha > alphaThreshold) break outer; } top++; } outer: for (int y = image.getHeight(); --y >= top;) { for (int x = 0; x < image.getWidth(); x++) { int pixel = image.getPixel(x, y); int alpha = ((pixel & 0x000000ff)); if (alpha > alphaThreshold) break outer; } bottom--; } } int left = 0; int right = image.getWidth(); if (stripWhitespaceX) { outer: for (int x = 0; x < image.getWidth(); x++) { for (int y = top; y < bottom; y++) { int pixel = image.getPixel(x, y); int alpha = ((pixel & 0x000000ff)); if (alpha > alphaThreshold) break outer; } left++; } outer: for (int x = image.getWidth(); --x >= left;) { for (int y = top; y < bottom; y++) { int pixel = image.getPixel(x, y); int alpha = ((pixel & 0x000000ff)); if (alpha > alphaThreshold) break outer; } right--; } } int newWidth = right - left; int newHeight = bottom - top; pixmapToDispose = new Pixmap(newWidth, newHeight, image.getFormat()); pixmapToDispose.setBlending(Blending.None); pixmapToDispose.drawPixmap(image, 0, 0, left, top, newWidth, newHeight); image = pixmapToDispose; rect = new PixmapPackerRectangle(0, 0, newWidth, newHeight, left, top, originalWidth, originalHeight); } else { rect = new PixmapPackerRectangle(0, 0, image.getWidth(), image.getHeight()); } } if (rect.getWidth() > pageWidth || rect.getHeight() > pageHeight) { if (name == null) throw new GdxRuntimeException("Page size too small for pixmap."); throw new GdxRuntimeException("Page size too small for pixmap: " + name); } Page page = packStrategy.pack(this, name, rect); if (name != null) { page.rects.put(name, rect); page.addedRects.add(name); } int rectX = (int)rect.x, rectY = (int)rect.y, rectWidth = (int)rect.width, rectHeight = (int)rect.height; if (packToTexture && !duplicateBorder && page.texture != null && !page.dirty) { page.texture.bind(); Gdx.gl.glTexSubImage2D(page.texture.glTarget, 0, rectX, rectY, rectWidth, rectHeight, image.getGLFormat(), image.getGLType(), image.getPixels()); } else page.dirty = true; page.image.drawPixmap(image, rectX, rectY); if (duplicateBorder) { int imageWidth = image.getWidth(), imageHeight = image.getHeight(); // Copy corner pixels to fill corners of the padding. page.image.drawPixmap(image, 0, 0, 1, 1, rectX - 1, rectY - 1, 1, 1); page.image.drawPixmap(image, imageWidth - 1, 0, 1, 1, rectX + rectWidth, rectY - 1, 1, 1); page.image.drawPixmap(image, 0, imageHeight - 1, 1, 1, rectX - 1, rectY + rectHeight, 1, 1); page.image.drawPixmap(image, imageWidth - 1, imageHeight - 1, 1, 1, rectX + rectWidth, rectY + rectHeight, 1, 1); // Copy edge pixels into padding. page.image.drawPixmap(image, 0, 0, imageWidth, 1, rectX, rectY - 1, rectWidth, 1); page.image.drawPixmap(image, 0, imageHeight - 1, imageWidth, 1, rectX, rectY + rectHeight, rectWidth, 1); page.image.drawPixmap(image, 0, 0, 1, imageHeight, rectX - 1, rectY, 1, rectHeight); page.image.drawPixmap(image, imageWidth - 1, 0, 1, imageHeight, rectX + rectWidth, rectY, 1, rectHeight); } if (pixmapToDispose != null) { pixmapToDispose.dispose(); } rect.page = page; return rect; } /** @return the {@link Page} instances created so far. If multiple threads are accessing the packer, iterating over the pages * must be done only after synchronizing on the packer. */ public Array getPages () { return pages; } /** @param name the name of the image * @return the rectangle for the image in the page it's stored in or null */ public synchronized Rectangle getRect (String name) { for (Page page : pages) { Rectangle rect = page.rects.get(name); if (rect != null) return rect; } return null; } /** @param name the name of the image * @return the page the image is stored in or null */ public synchronized Page getPage (String name) { for (Page page : pages) { Rectangle rect = page.rects.get(name); if (rect != null) return page; } return null; } /** Returns the index of the page containing the given packed rectangle. * @param name the name of the image * @return the index of the page the image is stored in or -1 */ public synchronized int getPageIndex (String name) { for (int i = 0; i < pages.size; i++) { Rectangle rect = pages.get(i).rects.get(name); if (rect != null) return i; } return -1; } /** Disposes any pixmap pages which don't have a texture. Page pixmaps that have a texture will not be disposed until their * texture is disposed. */ public synchronized void dispose () { for (Page page : pages) { if (page.texture == null) { page.image.dispose(); } } disposed = true; } /** Generates a new {@link TextureAtlas} from the pixmaps inserted so far. After calling this method, disposing the packer will * no longer dispose the page pixmaps. */ public synchronized TextureAtlas generateTextureAtlas (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { TextureAtlas atlas = new TextureAtlas(); updateTextureAtlas(atlas, minFilter, magFilter, useMipMaps); return atlas; } /** Updates the {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method. This * can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the TextureAtlas on * the rendering thread. This method must be called on the rendering thread. After calling this method, disposing the packer * will no longer dispose the page pixmaps. Has useIndexes on by default so as to keep backwards compatibility */ public synchronized void updateTextureAtlas (TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { updateTextureAtlas(atlas, minFilter, magFilter, useMipMaps, true); } /** Updates the {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method. This * can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the TextureAtlas on * the rendering thread. This method must be called on the rendering thread. After calling this method, disposing the packer * will no longer dispose the page pixmaps. */ public synchronized void updateTextureAtlas (TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps, boolean useIndexes) { updatePageTextures(minFilter, magFilter, useMipMaps); for (Page page : pages) { if (page.addedRects.size > 0) { for (String name : page.addedRects) { PixmapPackerRectangle rect = page.rects.get(name); TextureAtlas.AtlasRegion region = new TextureAtlas.AtlasRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); if (rect.splits != null) { region.names = new String[] {"split", "pad"}; region.values = new int[][] {rect.splits, rect.pads}; } int imageIndex = -1; String imageName = name; if (useIndexes) { Matcher matcher = indexPattern.matcher(imageName); if (matcher.matches()) { imageName = matcher.group(1); imageIndex = Integer.parseInt(matcher.group(2)); } } region.name = imageName; region.index = imageIndex; region.offsetX = rect.offsetX; region.offsetY = (int)(rect.originalHeight - rect.height - rect.offsetY); region.originalWidth = rect.originalWidth; region.originalHeight = rect.originalHeight; atlas.getRegions().add(region); } page.addedRects.clear(); atlas.getTextures().add(page.texture); } } } /** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page and adds a region to * the specified array for each page texture. */ public synchronized void updateTextureRegions (Array regions, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { updatePageTextures(minFilter, magFilter, useMipMaps); while (regions.size < pages.size) regions.add(new TextureRegion(pages.get(regions.size).texture)); } /** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page. */ public synchronized void updatePageTextures (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { for (Page page : pages) page.updateTexture(minFilter, magFilter, useMipMaps); } public int getPageWidth () { return pageWidth; } public void setPageWidth (int pageWidth) { this.pageWidth = pageWidth; } public int getPageHeight () { return pageHeight; } public void setPageHeight (int pageHeight) { this.pageHeight = pageHeight; } public Format getPageFormat () { return pageFormat; } public void setPageFormat (Format pageFormat) { this.pageFormat = pageFormat; } public int getPadding () { return padding; } public void setPadding (int padding) { this.padding = padding; } public boolean getDuplicateBorder () { return duplicateBorder; } public void setDuplicateBorder (boolean duplicateBorder) { this.duplicateBorder = duplicateBorder; } public boolean getPackToTexture () { return packToTexture; } /** 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 * updated using glTexSubImage2D. Note if packing many pixmaps, this may be slower than reuploading the whole texture. This * setting is ignored if {@link #getDuplicateBorder()} is true. */ public void setPackToTexture (boolean packToTexture) { this.packToTexture = packToTexture; } /** @author mzechner * @author Nathan Sweet * @author Rob Rendell */ static public class Page { OrderedMap rects = new OrderedMap(); Pixmap image; Texture texture; final Array addedRects = new Array(); boolean dirty; /** Creates a new page filled with the color provided by the {@link PixmapPacker#getTransparentColor()} */ public Page (PixmapPacker packer) { image = new Pixmap(packer.pageWidth, packer.pageHeight, packer.pageFormat); image.setBlending(Blending.None); image.setColor(packer.getTransparentColor()); image.fill(); } public Pixmap getPixmap () { return image; } public OrderedMap getRects () { return rects; } /** Returns the texture for this page, or null if the texture has not been created. * @see #updateTexture(TextureFilter, TextureFilter, boolean) */ public Texture getTexture () { return texture; } /** Creates the texture if it has not been created, else reuploads the entire page pixmap to the texture if the pixmap has * changed since this method was last called. * @return true if the texture was created or reuploaded. */ public boolean updateTexture (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { if (texture != null) { if (!dirty) return false; texture.load(texture.getTextureData()); } else { texture = new Texture(new PixmapTextureData(image, image.getFormat(), useMipMaps, false, true)) { @Override public void dispose () { super.dispose(); image.dispose(); } }; texture.setFilter(minFilter, magFilter); } dirty = false; return true; } } /** Choose the page and location for each rectangle. * @author Nathan Sweet */ static public interface PackStrategy { public void sort (Array images); /** Returns the page the rectangle should be placed in and modifies the specified rectangle position. */ public Page pack (PixmapPacker packer, String name, Rectangle rect); } /** Does bin packing by inserting to the right or below previously packed rectangles. This is good at packing arbitrarily sized * images. * @author mzechner * @author Nathan Sweet * @author Rob Rendell */ static public class GuillotineStrategy implements PackStrategy { Comparator comparator; public void sort (Array pixmaps) { if (comparator == null) { comparator = new Comparator() { public int compare (Pixmap o1, Pixmap o2) { return Math.max(o1.getWidth(), o1.getHeight()) - Math.max(o2.getWidth(), o2.getHeight()); } }; } pixmaps.sort(comparator); } public Page pack (PixmapPacker packer, String name, Rectangle rect) { GuillotinePage page; if (packer.pages.size == 0) { // Add a page if empty. page = new GuillotinePage(packer); packer.pages.add(page); } else { // Always try to pack into the last page. page = (GuillotinePage)packer.pages.peek(); } int padding = packer.padding; rect.width += padding; rect.height += padding; Node node = insert(page.root, rect); if (node == null) { // Didn't fit, pack into a new page. page = new GuillotinePage(packer); packer.pages.add(page); node = insert(page.root, rect); } node.full = true; rect.set(node.rect.x, node.rect.y, node.rect.width - padding, node.rect.height - padding); return page; } private Node insert (Node node, Rectangle rect) { if (!node.full && node.leftChild != null && node.rightChild != null) { Node newNode = insert(node.leftChild, rect); if (newNode == null) newNode = insert(node.rightChild, rect); return newNode; } else { if (node.full) return null; if (node.rect.width == rect.width && node.rect.height == rect.height) return node; if (node.rect.width < rect.width || node.rect.height < rect.height) return null; node.leftChild = new Node(); node.rightChild = new Node(); int deltaWidth = (int)node.rect.width - (int)rect.width; int deltaHeight = (int)node.rect.height - (int)rect.height; if (deltaWidth > deltaHeight) { node.leftChild.rect.x = node.rect.x; node.leftChild.rect.y = node.rect.y; node.leftChild.rect.width = rect.width; node.leftChild.rect.height = node.rect.height; node.rightChild.rect.x = node.rect.x + rect.width; node.rightChild.rect.y = node.rect.y; node.rightChild.rect.width = node.rect.width - rect.width; node.rightChild.rect.height = node.rect.height; } else { node.leftChild.rect.x = node.rect.x; node.leftChild.rect.y = node.rect.y; node.leftChild.rect.width = node.rect.width; node.leftChild.rect.height = rect.height; node.rightChild.rect.x = node.rect.x; node.rightChild.rect.y = node.rect.y + rect.height; node.rightChild.rect.width = node.rect.width; node.rightChild.rect.height = node.rect.height - rect.height; } return insert(node.leftChild, rect); } } static final class Node { public Node leftChild; public Node rightChild; public final Rectangle rect = new Rectangle(); public boolean full; } static class GuillotinePage extends Page { Node root; public GuillotinePage (PixmapPacker packer) { super(packer); root = new Node(); root.rect.x = packer.padding; root.rect.y = packer.padding; root.rect.width = packer.pageWidth - packer.padding * 2; root.rect.height = packer.pageHeight - packer.padding * 2; } } } /** Does bin packing by inserting in rows. This is good at packing images that have similar heights. * @author Nathan Sweet */ static public class SkylineStrategy implements PackStrategy { Comparator comparator; public void sort (Array images) { if (comparator == null) { comparator = new Comparator() { public int compare (Pixmap o1, Pixmap o2) { return o1.getHeight() - o2.getHeight(); } }; } images.sort(comparator); } public Page pack (PixmapPacker packer, String name, Rectangle rect) { int padding = packer.padding; int pageWidth = packer.pageWidth - padding * 2, pageHeight = packer.pageHeight - padding * 2; int rectWidth = (int)rect.width + padding, rectHeight = (int)rect.height + padding; for (int i = 0, n = packer.pages.size; i < n; i++) { SkylinePage page = (SkylinePage)packer.pages.get(i); Row bestRow = null; // Fit in any row before the last. for (int ii = 0, nn = page.rows.size - 1; ii < nn; ii++) { Row row = page.rows.get(ii); if (row.x + rectWidth >= pageWidth) continue; if (row.y + rectHeight >= pageHeight) continue; if (rectHeight > row.height) continue; if (bestRow == null || row.height < bestRow.height) bestRow = row; } if (bestRow == null) { // Fit in last row, increasing height. Row row = page.rows.peek(); if (row.y + rectHeight >= pageHeight) continue; if (row.x + rectWidth < pageWidth) { row.height = Math.max(row.height, rectHeight); bestRow = row; } else if (row.y + row.height + rectHeight < pageHeight) { // Fit in new row. bestRow = new Row(); bestRow.y = row.y + row.height; bestRow.height = rectHeight; page.rows.add(bestRow); } } if (bestRow != null) { rect.x = bestRow.x; rect.y = bestRow.y; bestRow.x += rectWidth; return page; } } // Fit in new page. SkylinePage page = new SkylinePage(packer); packer.pages.add(page); Row row = new Row(); row.x = padding + rectWidth; row.y = padding; row.height = rectHeight; page.rows.add(row); rect.x = padding; rect.y = padding; return page; } static class SkylinePage extends Page { Array rows = new Array(); public SkylinePage (PixmapPacker packer) { super(packer); } static class Row { int x, y, height; } } } /** @see PixmapPacker#setTransparentColor(Color color) */ public Color getTransparentColor () { return this.transparentColor; } /** Sets the default color of the whole {@link PixmapPacker.Page} when a new one created. Helps to avoid texture * bleeding or to highlight the page for debugging. * @see Page#Page(PixmapPacker packer) */ public void setTransparentColor (Color color) { this.transparentColor.set(color); } private int[] getSplits (Pixmap raster) { int startX = getSplitPoint(raster, 1, 0, true, true); int endX = getSplitPoint(raster, startX, 0, false, true); int startY = getSplitPoint(raster, 0, 1, true, false); int endY = getSplitPoint(raster, 0, startY, false, false); // Ensure pixels after the end are not invalid. getSplitPoint(raster, endX + 1, 0, true, true); getSplitPoint(raster, 0, endY + 1, true, false); // No splits, or all splits. if (startX == 0 && endX == 0 && startY == 0 && endY == 0) return null; // Subtraction here is because the coordinates were computed before the 1px border was stripped. if (startX != 0) { startX--; endX = raster.getWidth() - 2 - (endX - 1); } else { // If no start point was ever found, we assume full stretch. endX = raster.getWidth() - 2; } if (startY != 0) { startY--; endY = raster.getHeight() - 2 - (endY - 1); } else { // If no start point was ever found, we assume full stretch. endY = raster.getHeight() - 2; } return new int[] {startX, endX, startY, endY}; } private int[] getPads (Pixmap raster, int[] splits) { int bottom = raster.getHeight() - 1; int right = raster.getWidth() - 1; int startX = getSplitPoint(raster, 1, bottom, true, true); int startY = getSplitPoint(raster, right, 1, true, false); // No need to hunt for the end if a start was never found. int endX = 0; int endY = 0; if (startX != 0) endX = getSplitPoint(raster, startX + 1, bottom, false, true); if (startY != 0) endY = getSplitPoint(raster, right, startY + 1, false, false); // Ensure pixels after the end are not invalid. getSplitPoint(raster, endX + 1, bottom, true, true); getSplitPoint(raster, right, endY + 1, true, false); // No pads. if (startX == 0 && endX == 0 && startY == 0 && endY == 0) { return null; } // -2 here is because the coordinates were computed before the 1px border was stripped. if (startX == 0 && endX == 0) { startX = -1; endX = -1; } else { if (startX > 0) { startX--; endX = raster.getWidth() - 2 - (endX - 1); } else { // If no start point was ever found, we assume full stretch. endX = raster.getWidth() - 2; } } if (startY == 0 && endY == 0) { startY = -1; endY = -1; } else { if (startY > 0) { startY--; endY = raster.getHeight() - 2 - (endY - 1); } else { // If no start point was ever found, we assume full stretch. endY = raster.getHeight() - 2; } } int[] pads = new int[] {startX, endX, startY, endY}; if (splits != null && Arrays.equals(pads, splits)) { return null; } return pads; } private Color c = new Color(); private int getSplitPoint (Pixmap raster, int startX, int startY, boolean startPoint, boolean xAxis) { int[] rgba = new int[4]; int next = xAxis ? startX : startY; int end = xAxis ? raster.getWidth() : raster.getHeight(); int breakA = startPoint ? 255 : 0; int x = startX; int y = startY; while (next != end) { if (xAxis) x = next; else y = next; int colint = raster.getPixel(x, y); c.set(colint); rgba[0] = (int)(c.r * 255); rgba[1] = (int)(c.g * 255); rgba[2] = (int)(c.b * 255); rgba[3] = (int)(c.a * 255); if (rgba[3] == breakA) return next; if (!startPoint && (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255)) System.out.println(x + " " + y + " " + rgba + " "); next++; } return 0; } public static class PixmapPackerRectangle extends Rectangle { public Page page; public int[] splits; public int[] pads; public int offsetX, offsetY; public int originalWidth, originalHeight; PixmapPackerRectangle (int x, int y, int width, int height) { super(x, y, width, height); this.offsetX = 0; this.offsetY = 0; this.originalWidth = width; this.originalHeight = height; } PixmapPackerRectangle (int x, int y, int width, int height, int left, int top, int originalWidth, int originalHeight) { super(x, y, width, height); this.offsetX = left; this.offsetY = top; this.originalWidth = originalWidth; this.originalHeight = originalHeight; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy