gdx-studio
git clone https://git.pyrossh.dev/gdx-studio
An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop
src_libs/com/badlogic/gdx/tools/imagepacker/ImageProcessor.java
| a62d533 | 1 | /******************************************************************************* |
| a62d533 | 2 | * Copyright 2011 See AUTHORS file. |
| a62d533 | 3 | * |
| a62d533 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| a62d533 | 5 | * you may not use this file except in compliance with the License. |
| a62d533 | 6 | * You may obtain a copy of the License at |
| a62d533 | 7 | * |
| a62d533 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| a62d533 | 9 | * |
| a62d533 | 10 | * Unless required by applicable law or agreed to in writing, software |
| a62d533 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| a62d533 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| a62d533 | 13 | * See the License for the specific language governing permissions and |
| a62d533 | 14 | * limitations under the License. |
| a62d533 | 15 | ******************************************************************************/ |
| a62d533 | 16 | |
| a62d533 | 17 | package com.badlogic.gdx.tools.imagepacker; |
| a62d533 | 18 | |
| a62d533 | 19 | import com.badlogic.gdx.tools.imagepacker.TexturePacker2.Alias; |
| a62d533 | 20 | import com.badlogic.gdx.tools.imagepacker.TexturePacker2.Rect; |
| a62d533 | 21 | import com.badlogic.gdx.tools.imagepacker.TexturePacker2.Settings; |
| a62d533 | 22 | import com.badlogic.gdx.utils.Array; |
| a62d533 | 23 | |
| a62d533 | 24 | import java.awt.image.BufferedImage; |
| a62d533 | 25 | import java.awt.image.WritableRaster; |
| a62d533 | 26 | import java.io.File; |
| a62d533 | 27 | import java.io.IOException; |
| a62d533 | 28 | import java.math.BigInteger; |
| a62d533 | 29 | import java.security.MessageDigest; |
| a62d533 | 30 | import java.security.NoSuchAlgorithmException; |
| a62d533 | 31 | import java.util.Arrays; |
| a62d533 | 32 | import java.util.HashMap; |
| a62d533 | 33 | import java.util.regex.Matcher; |
| a62d533 | 34 | import java.util.regex.Pattern; |
| a62d533 | 35 | |
| a62d533 | 36 | import javax.imageio.ImageIO; |
| a62d533 | 37 | |
| a62d533 | 38 | public class ImageProcessor {
|
| a62d533 | 39 | static private final BufferedImage emptyImage = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR); |
| a62d533 | 40 | static private Pattern indexPattern = Pattern.compile("(.+)_(\\d+)$");
|
| a62d533 | 41 | |
| a62d533 | 42 | private String rootPath; |
| a62d533 | 43 | private final Settings settings; |
| a62d533 | 44 | private final HashMap<String, Rect> crcs = new HashMap(); |
| a62d533 | 45 | private final Array<Rect> rects = new Array(); |
| a62d533 | 46 | |
| a62d533 | 47 | /** @param rootDir Can be null. */ |
| a62d533 | 48 | public ImageProcessor (File rootDir, Settings settings) {
|
| a62d533 | 49 | this.settings = settings; |
| a62d533 | 50 | |
| a62d533 | 51 | if (rootDir != null) {
|
| a62d533 | 52 | rootPath = rootDir.getAbsolutePath().replace('\\', '/');
|
| a62d533 | 53 | if (!rootPath.endsWith("/")) rootPath += "/";
|
| a62d533 | 54 | } |
| a62d533 | 55 | } |
| a62d533 | 56 | |
| a62d533 | 57 | public ImageProcessor (Settings settings) {
|
| a62d533 | 58 | this(null, settings); |
| a62d533 | 59 | } |
| a62d533 | 60 | |
| a62d533 | 61 | public void addImage (File file) {
|
| a62d533 | 62 | BufferedImage image; |
| a62d533 | 63 | try {
|
| a62d533 | 64 | image = ImageIO.read(file); |
| a62d533 | 65 | } catch (IOException ex) {
|
| a62d533 | 66 | throw new RuntimeException("Error reading image: " + file, ex);
|
| a62d533 | 67 | } |
| a62d533 | 68 | if (image == null) throw new RuntimeException("Unable to read image: " + file);
|
| a62d533 | 69 | |
| a62d533 | 70 | // Strip root dir off front of image path. |
| a62d533 | 71 | String name = file.getAbsolutePath().replace('\\', '/');
|
| a62d533 | 72 | if (!name.startsWith(rootPath)) throw new RuntimeException("Path '" + name + "' does not start with root: " + rootPath);
|
| a62d533 | 73 | name = name.substring(rootPath.length()); |
| a62d533 | 74 | |
| a62d533 | 75 | // Strip extension. |
| a62d533 | 76 | int dotIndex = name.lastIndexOf('.');
|
| a62d533 | 77 | if (dotIndex != -1) name = name.substring(0, dotIndex); |
| a62d533 | 78 | |
| a62d533 | 79 | addImage(image, name); |
| a62d533 | 80 | } |
| a62d533 | 81 | |
| a62d533 | 82 | public void addImage (BufferedImage image, String name) {
|
| a62d533 | 83 | if (image.getType() != BufferedImage.TYPE_4BYTE_ABGR) {
|
| a62d533 | 84 | BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); |
| a62d533 | 85 | newImage.getGraphics().drawImage(image, 0, 0, null); |
| a62d533 | 86 | image = newImage; |
| a62d533 | 87 | } |
| a62d533 | 88 | |
| a62d533 | 89 | Rect rect = null; |
| a62d533 | 90 | |
| a62d533 | 91 | // Strip ".9" from file name, read ninepatch split pixels, and strip ninepatch split pixels. |
| a62d533 | 92 | int[] splits = null; |
| a62d533 | 93 | int[] pads = null; |
| a62d533 | 94 | if (name.endsWith(".9")) {
|
| a62d533 | 95 | name = name.substring(0, name.length() - 2); |
| a62d533 | 96 | splits = getSplits(image, name); |
| a62d533 | 97 | pads = getPads(image, name, splits); |
| a62d533 | 98 | // Strip split pixels. |
| a62d533 | 99 | BufferedImage newImage = new BufferedImage(image.getWidth() - 2, image.getHeight() - 2, BufferedImage.TYPE_4BYTE_ABGR); |
| a62d533 | 100 | newImage.getGraphics().drawImage(image, 0, 0, newImage.getWidth(), newImage.getHeight(), 1, 1, image.getWidth() - 1, |
| a62d533 | 101 | image.getHeight() - 1, null); |
| a62d533 | 102 | image = newImage; |
| a62d533 | 103 | // Ninepatches won't be rotated or whitespace stripped. |
| a62d533 | 104 | rect = new Rect(image, 0, 0, image.getWidth(), image.getHeight()); |
| a62d533 | 105 | rect.splits = splits; |
| a62d533 | 106 | rect.pads = pads; |
| a62d533 | 107 | rect.canRotate = false; |
| a62d533 | 108 | } |
| a62d533 | 109 | |
| a62d533 | 110 | // Strip digits off end of name and use as index. |
| a62d533 | 111 | int index = -1; |
| a62d533 | 112 | if (settings.useIndexes) {
|
| a62d533 | 113 | Matcher matcher = indexPattern.matcher(name); |
| a62d533 | 114 | if (matcher.matches()) {
|
| a62d533 | 115 | name = matcher.group(1); |
| a62d533 | 116 | index = Integer.parseInt(matcher.group(2)); |
| a62d533 | 117 | } |
| a62d533 | 118 | } |
| a62d533 | 119 | |
| a62d533 | 120 | if (rect == null) {
|
| a62d533 | 121 | rect = createRect(image); |
| a62d533 | 122 | if (rect == null) {
|
| a62d533 | 123 | System.out.println("Ignoring blank input image: " + name);
|
| a62d533 | 124 | return; |
| a62d533 | 125 | } |
| a62d533 | 126 | } |
| a62d533 | 127 | |
| a62d533 | 128 | rect.name = name; |
| a62d533 | 129 | rect.index = index; |
| a62d533 | 130 | |
| a62d533 | 131 | if (settings.alias) {
|
| a62d533 | 132 | String crc = hash(rect.image); |
| a62d533 | 133 | Rect existing = crcs.get(crc); |
| a62d533 | 134 | if (existing != null) {
|
| a62d533 | 135 | System.out.println(rect.name + " (alias of " + existing.name + ")"); |
| a62d533 | 136 | existing.aliases.add(new Alias(rect)); |
| a62d533 | 137 | return; |
| a62d533 | 138 | } |
| a62d533 | 139 | crcs.put(crc, rect); |
| a62d533 | 140 | } |
| a62d533 | 141 | |
| a62d533 | 142 | rects.add(rect); |
| a62d533 | 143 | } |
| a62d533 | 144 | |
| a62d533 | 145 | public Array<Rect> getImages () {
|
| a62d533 | 146 | return rects; |
| a62d533 | 147 | } |
| a62d533 | 148 | |
| a62d533 | 149 | /** Strips whitespace and returns the rect, or null if the image should be ignored. */ |
| a62d533 | 150 | private Rect createRect (BufferedImage source) {
|
| a62d533 | 151 | WritableRaster alphaRaster = source.getAlphaRaster(); |
| a62d533 | 152 | if (alphaRaster == null || (!settings.stripWhitespaceX && !settings.stripWhitespaceY)) |
| a62d533 | 153 | return new Rect(source, 0, 0, source.getWidth(), source.getHeight()); |
| a62d533 | 154 | final byte[] a = new byte[1]; |
| a62d533 | 155 | int top = 0; |
| a62d533 | 156 | int bottom = source.getHeight(); |
| a62d533 | 157 | if (settings.stripWhitespaceX) {
|
| a62d533 | 158 | outer: |
| a62d533 | 159 | for (int y = 0; y < source.getHeight(); y++) {
|
| a62d533 | 160 | for (int x = 0; x < source.getWidth(); x++) {
|
| a62d533 | 161 | alphaRaster.getDataElements(x, y, a); |
| a62d533 | 162 | int alpha = a[0]; |
| a62d533 | 163 | if (alpha < 0) alpha += 256; |
| a62d533 | 164 | if (alpha > settings.alphaThreshold) break outer; |
| a62d533 | 165 | } |
| a62d533 | 166 | top++; |
| a62d533 | 167 | } |
| a62d533 | 168 | outer: |
| a62d533 | 169 | for (int y = source.getHeight(); --y >= top;) {
|
| a62d533 | 170 | for (int x = 0; x < source.getWidth(); x++) {
|
| a62d533 | 171 | alphaRaster.getDataElements(x, y, a); |
| a62d533 | 172 | int alpha = a[0]; |
| a62d533 | 173 | if (alpha < 0) alpha += 256; |
| a62d533 | 174 | if (alpha > settings.alphaThreshold) break outer; |
| a62d533 | 175 | } |
| a62d533 | 176 | bottom--; |
| a62d533 | 177 | } |
| a62d533 | 178 | } |
| a62d533 | 179 | int left = 0; |
| a62d533 | 180 | int right = source.getWidth(); |
| a62d533 | 181 | if (settings.stripWhitespaceY) {
|
| a62d533 | 182 | outer: |
| a62d533 | 183 | for (int x = 0; x < source.getWidth(); x++) {
|
| a62d533 | 184 | for (int y = top; y < bottom; y++) {
|
| a62d533 | 185 | alphaRaster.getDataElements(x, y, a); |
| a62d533 | 186 | int alpha = a[0]; |
| a62d533 | 187 | if (alpha < 0) alpha += 256; |
| a62d533 | 188 | if (alpha > settings.alphaThreshold) break outer; |
| a62d533 | 189 | } |
| a62d533 | 190 | left++; |
| a62d533 | 191 | } |
| a62d533 | 192 | outer: |
| a62d533 | 193 | for (int x = source.getWidth(); --x >= left;) {
|
| a62d533 | 194 | for (int y = top; y < bottom; y++) {
|
| a62d533 | 195 | alphaRaster.getDataElements(x, y, a); |
| a62d533 | 196 | int alpha = a[0]; |
| a62d533 | 197 | if (alpha < 0) alpha += 256; |
| a62d533 | 198 | if (alpha > settings.alphaThreshold) break outer; |
| a62d533 | 199 | } |
| a62d533 | 200 | right--; |
| a62d533 | 201 | } |
| a62d533 | 202 | } |
| a62d533 | 203 | int newWidth = right - left; |
| a62d533 | 204 | int newHeight = bottom - top; |
| a62d533 | 205 | if (newWidth <= 0 || newHeight <= 0) {
|
| a62d533 | 206 | if (settings.ignoreBlankImages) |
| a62d533 | 207 | return null; |
| a62d533 | 208 | else |
| a62d533 | 209 | return new Rect(emptyImage, 0, 0, 1, 1); |
| a62d533 | 210 | } |
| a62d533 | 211 | return new Rect(source, left, top, newWidth, newHeight); |
| a62d533 | 212 | } |
| a62d533 | 213 | |
| a62d533 | 214 | private String splitError (int x, int y, int[] rgba, String name) {
|
| a62d533 | 215 | throw new RuntimeException("Invalid " + name + " ninepatch split pixel at " + x + ", " + y + ", rgba: " + rgba[0] + ", "
|
| a62d533 | 216 | + rgba[1] + ", " + rgba[2] + ", " + rgba[3]); |
| a62d533 | 217 | } |
| a62d533 | 218 | |
| a62d533 | 219 | /** Returns the splits, or null if the image had no splits or the splits were only a single region. Splits are an int[4] that |
| a62d533 | 220 | * has left, right, top, bottom. */ |
| a62d533 | 221 | private int[] getSplits (BufferedImage image, String name) {
|
| a62d533 | 222 | WritableRaster raster = image.getRaster(); |
| a62d533 | 223 | |
| a62d533 | 224 | int startX = getSplitPoint(raster, name, 1, 0, true, true); |
| a62d533 | 225 | int endX = getSplitPoint(raster, name, startX, 0, false, true); |
| a62d533 | 226 | int startY = getSplitPoint(raster, name, 0, 1, true, false); |
| a62d533 | 227 | int endY = getSplitPoint(raster, name, 0, startY, false, false); |
| a62d533 | 228 | |
| a62d533 | 229 | // Ensure pixels after the end are not invalid. |
| a62d533 | 230 | getSplitPoint(raster, name, endX + 1, 0, true, true); |
| a62d533 | 231 | getSplitPoint(raster, name, 0, endY + 1, true, false); |
| a62d533 | 232 | |
| a62d533 | 233 | // No splits, or all splits. |
| a62d533 | 234 | if (startX == 0 && endX == 0 && startY == 0 && endY == 0) return null; |
| a62d533 | 235 | |
| a62d533 | 236 | // Subtraction here is because the coordinates were computed before the 1px border was stripped. |
| a62d533 | 237 | if (startX != 0) {
|
| a62d533 | 238 | startX--; |
| a62d533 | 239 | endX = raster.getWidth() - 2 - (endX - 1); |
| a62d533 | 240 | } else {
|
| a62d533 | 241 | // If no start point was ever found, we assume full stretch. |
| a62d533 | 242 | endX = raster.getWidth() - 2; |
| a62d533 | 243 | } |
| a62d533 | 244 | if (startY != 0) {
|
| a62d533 | 245 | startY--; |
| a62d533 | 246 | endY = raster.getHeight() - 2 - (endY - 1); |
| a62d533 | 247 | } else {
|
| a62d533 | 248 | // If no start point was ever found, we assume full stretch. |
| a62d533 | 249 | endY = raster.getHeight() - 2; |
| a62d533 | 250 | } |
| a62d533 | 251 | |
| a62d533 | 252 | return new int[] {startX, endX, startY, endY};
|
| a62d533 | 253 | } |
| a62d533 | 254 | |
| a62d533 | 255 | /** Returns the pads, or null if the image had no pads or the pads match the splits. Pads are an int[4] that has left, right, |
| a62d533 | 256 | * top, bottom. */ |
| a62d533 | 257 | private int[] getPads (BufferedImage image, String name, int[] splits) {
|
| a62d533 | 258 | WritableRaster raster = image.getRaster(); |
| a62d533 | 259 | |
| a62d533 | 260 | int bottom = raster.getHeight() - 1; |
| a62d533 | 261 | int right = raster.getWidth() - 1; |
| a62d533 | 262 | |
| a62d533 | 263 | int startX = getSplitPoint(raster, name, 1, bottom, true, true); |
| a62d533 | 264 | int startY = getSplitPoint(raster, name, right, 1, true, false); |
| a62d533 | 265 | |
| a62d533 | 266 | // No need to hunt for the end if a start was never found. |
| a62d533 | 267 | int endX = 0; |
| a62d533 | 268 | int endY = 0; |
| a62d533 | 269 | if (startX != 0) endX = getSplitPoint(raster, name, startX + 1, bottom, false, true); |
| a62d533 | 270 | if (startY != 0) endY = getSplitPoint(raster, name, right, startY + 1, false, false); |
| a62d533 | 271 | |
| a62d533 | 272 | // Ensure pixels after the end are not invalid. |
| a62d533 | 273 | getSplitPoint(raster, name, endX + 1, bottom, true, true); |
| a62d533 | 274 | getSplitPoint(raster, name, right, endY + 1, true, false); |
| a62d533 | 275 | |
| a62d533 | 276 | // No pads. |
| a62d533 | 277 | if (startX == 0 && endX == 0 && startY == 0 && endY == 0) {
|
| a62d533 | 278 | return null; |
| a62d533 | 279 | } |
| a62d533 | 280 | |
| a62d533 | 281 | // -2 here is because the coordinates were computed before the 1px border was stripped. |
| a62d533 | 282 | if (startX == 0 && endX == 0) {
|
| a62d533 | 283 | startX = -1; |
| a62d533 | 284 | endX = -1; |
| a62d533 | 285 | } else {
|
| a62d533 | 286 | if (startX > 0) {
|
| a62d533 | 287 | startX--; |
| a62d533 | 288 | endX = raster.getWidth() - 2 - (endX - 1); |
| a62d533 | 289 | } else {
|
| a62d533 | 290 | // If no start point was ever found, we assume full stretch. |
| a62d533 | 291 | endX = raster.getWidth() - 2; |
| a62d533 | 292 | } |
| a62d533 | 293 | } |
| a62d533 | 294 | if (startY == 0 && endY == 0) {
|
| a62d533 | 295 | startY = -1; |
| a62d533 | 296 | endY = -1; |
| a62d533 | 297 | } else {
|
| a62d533 | 298 | if (startY > 0) {
|
| a62d533 | 299 | startY--; |
| a62d533 | 300 | endY = raster.getHeight() - 2 - (endY - 1); |
| a62d533 | 301 | } else {
|
| a62d533 | 302 | // If no start point was ever found, we assume full stretch. |
| a62d533 | 303 | endY = raster.getHeight() - 2; |
| a62d533 | 304 | } |
| a62d533 | 305 | } |
| a62d533 | 306 | |
| a62d533 | 307 | int[] pads = new int[] {startX, endX, startY, endY};
|
| a62d533 | 308 | |
| a62d533 | 309 | if (splits != null && Arrays.equals(pads, splits)) {
|
| a62d533 | 310 | return null; |
| a62d533 | 311 | } |
| a62d533 | 312 | |
| a62d533 | 313 | return pads; |
| a62d533 | 314 | } |
| a62d533 | 315 | |
| a62d533 | 316 | /** Hunts for the start or end of a sequence of split pixels. Begins searching at (startX, startY) then follows along the x or y |
| a62d533 | 317 | * axis (depending on value of xAxis) for the first non-transparent pixel if startPoint is true, or the first transparent pixel |
| a62d533 | 318 | * if startPoint is false. Returns 0 if none found, as 0 is considered an invalid split point being in the outer border which |
| a62d533 | 319 | * will be stripped. */ |
| a62d533 | 320 | private int getSplitPoint (WritableRaster raster, String name, int startX, int startY, boolean startPoint, boolean xAxis) {
|
| a62d533 | 321 | int[] rgba = new int[4]; |
| a62d533 | 322 | |
| a62d533 | 323 | int next = xAxis ? startX : startY; |
| a62d533 | 324 | int end = xAxis ? raster.getWidth() : raster.getHeight(); |
| a62d533 | 325 | int breakA = startPoint ? 255 : 0; |
| a62d533 | 326 | |
| a62d533 | 327 | int x = startX; |
| a62d533 | 328 | int y = startY; |
| a62d533 | 329 | while (next != end) {
|
| a62d533 | 330 | if (xAxis) |
| a62d533 | 331 | x = next; |
| a62d533 | 332 | else |
| a62d533 | 333 | y = next; |
| a62d533 | 334 | |
| a62d533 | 335 | raster.getPixel(x, y, rgba); |
| a62d533 | 336 | if (rgba[3] == breakA) return next; |
| a62d533 | 337 | |
| a62d533 | 338 | if (!startPoint && (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255)) splitError(x, y, rgba, name); |
| a62d533 | 339 | |
| a62d533 | 340 | next++; |
| a62d533 | 341 | } |
| a62d533 | 342 | |
| a62d533 | 343 | return 0; |
| a62d533 | 344 | } |
| a62d533 | 345 | |
| a62d533 | 346 | static private String hash (BufferedImage image) {
|
| a62d533 | 347 | try {
|
| a62d533 | 348 | MessageDigest digest = MessageDigest.getInstance("SHA1");
|
| a62d533 | 349 | |
| a62d533 | 350 | // Ensure image is the correct format. |
| a62d533 | 351 | int width = image.getWidth(); |
| a62d533 | 352 | int height = image.getHeight(); |
| a62d533 | 353 | if (image.getType() != BufferedImage.TYPE_INT_ARGB) {
|
| a62d533 | 354 | BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
| a62d533 | 355 | newImage.getGraphics().drawImage(image, 0, 0, null); |
| a62d533 | 356 | image = newImage; |
| a62d533 | 357 | } |
| a62d533 | 358 | |
| a62d533 | 359 | WritableRaster raster = image.getRaster(); |
| a62d533 | 360 | int[] pixels = new int[width]; |
| a62d533 | 361 | for (int y = 0; y < height; y++) {
|
| a62d533 | 362 | raster.getDataElements(0, y, width, 1, pixels); |
| a62d533 | 363 | for (int x = 0; x < width; x++) |
| a62d533 | 364 | hash(digest, pixels[x]); |
| a62d533 | 365 | } |
| a62d533 | 366 | |
| a62d533 | 367 | hash(digest, width); |
| a62d533 | 368 | hash(digest, height); |
| a62d533 | 369 | |
| a62d533 | 370 | return new BigInteger(1, digest.digest()).toString(16); |
| a62d533 | 371 | } catch (NoSuchAlgorithmException ex) {
|
| a62d533 | 372 | throw new RuntimeException(ex); |
| a62d533 | 373 | } |
| a62d533 | 374 | } |
| a62d533 | 375 | |
| a62d533 | 376 | static private void hash (MessageDigest digest, int value) {
|
| a62d533 | 377 | digest.update((byte)(value >> 24)); |
| a62d533 | 378 | digest.update((byte)(value >> 16)); |
| a62d533 | 379 | digest.update((byte)(value >> 8)); |
| a62d533 | 380 | digest.update((byte)value); |
| a62d533 | 381 | } |
| a62d533 | 382 | } |