gdx-studio

#libgdx#java#desktop

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
}