├── .gitignore ├── README.md ├── java ├── awt │ └── annotations.xml ├── io │ └── annotations.xml ├── lang │ └── annotations.xml └── util │ ├── annotations.xml │ └── zip │ └── annotations.xml ├── javafx ├── scene │ ├── annotations.xml │ ├── image │ │ └── annotations.xml │ └── paint │ │ └── annotations.xml └── stage │ └── annotations.xml ├── javax └── imageio │ └── annotations.xml ├── pom.xml └── src ├── cursor.png ├── icon.png └── main ├── java ├── mayaseii │ └── wildsmoddingtool │ │ ├── AnimatedGIFWriter.java │ │ ├── App.java │ │ ├── Application.java │ │ ├── ColorMapper.java │ │ ├── Controller.java │ │ ├── GifSequenceWriter.java │ │ ├── PlayerSkinController.java │ │ └── Vector2.java └── module-info.java └── resources ├── META-INF └── MANIFEST.MF └── mayaseii └── wildsmoddingtool ├── PokeWilds-Regular.ttf ├── app.css ├── audio ├── BG_AzaleaTown.mp3 ├── BG_AzaleaTown.wav ├── BG_VioletCity.wav └── click.wav ├── img ├── background.png ├── buttonBG.png ├── buttonBiomeBG.png ├── buttonMusicBG.png ├── buttonPlayerBG.png ├── buttonPokemonBG.png ├── buttonResourceBG.png ├── buttonTextureBG.png ├── noVolume.png ├── player-skin │ ├── back.png │ ├── fishing-back.png │ ├── fishing-front.png │ ├── fishing-side.png │ ├── frame-colours.png │ ├── frame-error.png │ ├── frame-large.png │ ├── frame-long.png │ ├── frame-medium-long.png │ ├── frame-medium.png │ ├── frame-selection.png │ ├── frame-short.png │ ├── frame-xlarge.png │ ├── frame.png │ ├── front.png │ ├── generate-btn.png │ ├── idle-back.png │ ├── idle-front.png │ ├── idle-left.png │ ├── idle-right.png │ ├── name-bg.png │ ├── preview-bg.png │ ├── preview-btn.png │ ├── run-back.png │ ├── run-front.png │ ├── run-idle-back.png │ ├── run-idle-front.png │ ├── run-idle-left.png │ ├── run-idle-right.png │ ├── run-left.png │ ├── run-right.png │ ├── sitting-back.png │ ├── sitting-front.png │ ├── sitting-side.png │ ├── sleeping.png │ ├── walk-back.png │ ├── walk-front.png │ ├── walk-left.png │ └── walk-right.png ├── toolbarBG.png └── volume.png ├── main-menu.fxml ├── misc └── Wilds_PlayerSkinTemplate.zip ├── player-skin.css ├── player-skin.fxml ├── strings.properties ├── strings_es.properties ├── strings_fr.properties ├── strings_jp.properties └── strings_pt.properties /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java template 2 | *.class 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | ### JetBrains template 15 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 16 | 17 | *.iml 18 | 19 | ## Directory-based project format: 20 | .idea/ 21 | # if you remove the above rule, at least ignore the following: 22 | 23 | # User-specific stuff: 24 | # .idea/workspace.xml 25 | # .idea/tasks.xml 26 | # .idea/dictionaries 27 | 28 | # Sensitive or high-churn files: 29 | # .idea/dataSources.ids 30 | # .idea/dataSources.xml 31 | # .idea/sqlDataSources.xml 32 | # .idea/dynamic.xml 33 | # .idea/uiDesigner.xml 34 | 35 | # Gradle: 36 | # .idea/gradle.xml 37 | # .idea/libraries 38 | 39 | # Mongo Explorer plugin: 40 | # .idea/mongoSettings.xml 41 | 42 | ## File-based project format: 43 | *.ipr 44 | *.iws 45 | 46 | ## Plugin-specific files: 47 | 48 | # IntelliJ 49 | /out/ 50 | target/ 51 | 52 | # mpeltonen/sbt-idea plugin 53 | .idea_modules/ 54 | 55 | # JIRA plugin 56 | atlassian-ide-plugin.xml 57 | 58 | # Crashlytics plugin (for Android Studio and IntelliJ) 59 | com_crashlytics_export_strings.xml 60 | crashlytics.properties 61 | crashlytics-build.properties 62 | 63 | # Created by .ignore support plugin (hsz.mobi) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wilds Modding Tool 2 | Official modding tool for PokeWilds. 3 | 4 | ### Important requirements: 5 | 6 | Make sure you have **JRE 1.8** installed on your machine, as well as **JDK 17 or higher**. 7 |
Run the `.jar` file with the JRE. 8 | 9 | ### How to help out: 10 | 11 | Please report any kind of bugs and issues in the Issues section of the repository. There oughta be plenty! 12 | -------------------------------------------------------------------------------- /java/awt/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/io/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /java/lang/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/util/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/util/zip/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /javafx/scene/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /javafx/scene/image/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /javafx/scene/paint/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /javafx/stage/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /javax/imageio/annotations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | mayaseii 8 | WildsModdingTool 9 | 1.0-SNAPSHOT 10 | WildsModdingTool 11 | 12 | 13 | UTF-8 14 | 5.7.1 15 | 16 | 17 | 18 | 19 | org.openjfx 20 | javafx-controls 21 | 17.0.0.1 22 | 23 | 24 | org.openjfx 25 | javafx-fxml 26 | 17.0.0.1 27 | 28 | 29 | org.openjfx 30 | javafx-web 31 | 17.0.0.1 32 | 33 | 34 | org.controlsfx 35 | controlsfx 36 | 11.1.0 37 | 38 | 39 | com.dlsc.formsfx 40 | formsfx-core 41 | 11.3.2 42 | 43 | 44 | org.openjfx 45 | * 46 | 47 | 48 | 49 | 50 | net.synedra 51 | validatorfx 52 | 0.1.13 53 | 54 | 55 | org.openjfx 56 | * 57 | 58 | 59 | 60 | 61 | org.kordamp.ikonli 62 | ikonli-javafx 63 | 12.2.0 64 | 65 | 66 | org.kordamp.bootstrapfx 67 | bootstrapfx-core 68 | 0.4.0 69 | 70 | 71 | eu.hansolo 72 | tilesfx 73 | 11.48 74 | 75 | 76 | org.openjfx 77 | * 78 | 79 | 80 | 81 | 82 | org.junit.jupiter 83 | junit-jupiter-api 84 | ${junit.version} 85 | test 86 | 87 | 88 | org.junit.jupiter 89 | junit-jupiter-engine 90 | ${junit.version} 91 | test 92 | 93 | 94 | org.jetbrains 95 | annotations 96 | RELEASE 97 | compile 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-compiler-plugin 106 | 3.8.1 107 | 108 | 17 109 | 17 110 | 111 | 112 | 113 | org.openjfx 114 | javafx-maven-plugin 115 | 0.0.7 116 | 117 | 118 | 119 | default-cli 120 | 121 | mayaseii.wildsmoddingtool/mayaseii.wildsmoddingtool.Application 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/cursor.png -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/icon.png -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/AnimatedGIFWriter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2016 by Wen Yu. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Any modifications to this file must keep this entire header intact. 9 | * 10 | * Change History - most recent changes go on top of previous changes 11 | * 12 | * AnimatedGIFWriter.java 13 | * 14 | * Who Date Description 15 | * ==== ========= ================================================= 16 | * WY 29Oct2015 Crop image if outside of logical screen 17 | * WY 28Oct2015 Initial creation 18 | */ 19 | 20 | package mayaseii.wildsmoddingtool; 21 | 22 | import java.awt.Dimension; 23 | import java.awt.image.BufferedImage; 24 | import java.awt.image.DataBuffer; 25 | import java.awt.image.IndexColorModel; 26 | import java.awt.image.Raster; 27 | import java.io.IOException; 28 | import java.io.OutputStream; 29 | import java.util.Arrays; 30 | import java.util.List; 31 | 32 | public class AnimatedGIFWriter { 33 | // Fields 34 | private int codeLen; 35 | private int codeIndex; 36 | private int clearCode; 37 | private int endOfImage; 38 | private int bufIndex; 39 | private int empty_bits = 0x08; 40 | 41 | private int bitsPerPixel = 0x08; 42 | 43 | private byte bytes_buf[] = new byte[256]; 44 | private int[] colorPalette; 45 | private boolean isApplyDither; 46 | 47 | /** 48 | * A child is made up of a parent(or prefix) code plus a suffix color 49 | * and siblings are strings with a common parent(or prefix) and different 50 | * suffix colors 51 | */ 52 | int child[] = new int[4097]; 53 | 54 | int siblings[] = new int[4097]; 55 | int suffix[] = new int[4097]; 56 | 57 | private int logicalScreenWidth; 58 | private int logicalScreenHeight; 59 | 60 | private boolean animated; 61 | private int loopCount; 62 | private boolean firstFrame = true; 63 | 64 | // Define constants 65 | public static final byte IMAGE_SEPARATOR = 0x2c; // "," 66 | public static final byte IMAGE_TRAILER = 0x3b; // ";" 67 | public static final byte EXTENSION_INTRODUCER = 0x21; // "!" 68 | public static final byte GRAPHIC_CONTROL_LABEL = (byte)0xf9; 69 | public static final byte APPLICATION_EXTENSION_LABEL = (byte)0xff; 70 | public static final byte COMMENT_EXTENSION_LABEL = (byte)0xfe; 71 | public static final byte TEXT_EXTENSION_LABEL = 0x01; 72 | 73 | private static final int MASK[] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; 74 | 75 | private static Dimension getLogicalScreenSize(BufferedImage[] images) { 76 | // Determine the logical screen dimension assuming all the frames have the same 77 | // left and top coordinates (0, 0) 78 | int logicalScreenWidth = 0; 79 | int logicalScreenHeight = 0; 80 | 81 | for(BufferedImage image : images) { 82 | if(image.getWidth() > logicalScreenWidth) 83 | logicalScreenWidth = image.getWidth(); 84 | if(image.getHeight() > logicalScreenHeight) 85 | logicalScreenHeight = image.getHeight(); 86 | } 87 | 88 | return new Dimension(logicalScreenWidth, logicalScreenHeight); 89 | } 90 | 91 | private static Dimension getLogicalScreenSize(GIFFrame[] frames) { 92 | // Determine the logical screen dimension given all the frames with different 93 | // left and top coordinates. 94 | int logicalScreenWidth = 0; 95 | int logicalScreenHeight = 0; 96 | 97 | for(GIFFrame frame : frames) { 98 | int frameRightPosition = frame.getFrameWidth() + frame.getLeftPosition(); 99 | int frameBottomPosition = frame.getFrameHeight() + frame.getTopPosition(); 100 | if(frameRightPosition > logicalScreenWidth) 101 | logicalScreenWidth = frameRightPosition; 102 | if(frameBottomPosition > logicalScreenHeight) 103 | logicalScreenHeight = frameBottomPosition; 104 | } 105 | 106 | return new Dimension(logicalScreenWidth, logicalScreenHeight); 107 | } 108 | 109 | public AnimatedGIFWriter() { this(false);} 110 | 111 | public AnimatedGIFWriter(boolean isApplyDither) { 112 | this.isApplyDither = isApplyDither; 113 | } 114 | 115 | // Write as a single frame GIF 116 | public void write(BufferedImage img, OutputStream os) throws Exception { 117 | if(img == null) throw new NullPointerException("Input image is null"); 118 | int imageWidth = img.getWidth(); 119 | int imageHeight = img.getHeight(); 120 | write(getRGB(img), imageWidth, imageHeight, os); 121 | } 122 | 123 | private void encode(byte[] pixels, OutputStream os) throws Exception { 124 | // Define local variables 125 | int parent = 0; 126 | int son = 0; 127 | int brother = 0; 128 | int color = 0; 129 | int index = 0; 130 | int dimension = pixels.length; 131 | 132 | // Write out the length of the root 133 | os.write(bitsPerPixel = (bitsPerPixel == 1)?2:bitsPerPixel); 134 | // Initialize the encoder 135 | init_encoder(bitsPerPixel); 136 | // Tell the decoder to initialize the string table 137 | send_code_to_buffer(clearCode, os); 138 | // Get the first color and assign it to parent 139 | parent = (pixels[index++]&0xff); 140 | 141 | while (index < dimension) 142 | { 143 | color = (pixels[index++]&0xff); 144 | son = child[parent]; 145 | 146 | if ( son > 0){ 147 | if (suffix[son] == color) { 148 | parent = son; 149 | } else { 150 | brother = son; 151 | while (true) 152 | { 153 | if (siblings[brother] > 0) 154 | { 155 | brother = siblings[brother]; 156 | if (suffix[brother] == color) 157 | { 158 | parent = brother; 159 | break; 160 | } 161 | } else { 162 | siblings[brother] = codeIndex; 163 | suffix[codeIndex] = color; 164 | send_code_to_buffer(parent,os); 165 | parent = color; 166 | codeIndex++; 167 | // Check code length 168 | if(codeIndex > ((1< ((1<>24)&0xff); 268 | byte red = (byte)(((rgbs[i]>>16)&0xff)*alpha); 269 | byte green = (byte)(((rgbs[i]>>8)&0xff)*alpha); 270 | byte blue = (byte)((rgbs[i]&0xff)*alpha); 271 | rgbs[i] = (rgbs[i]&0xff000000)|((red&0xff)<<16)|((green&0xff)<<8)|(blue&0xff); 272 | } 273 | } else if(type == BufferedImage.TYPE_INT_BGR) { // Convert BGR to RGB 274 | for(int i = 0; i < rgbs.length; i++) { 275 | int blue = (rgbs[i]>>16)&0xff; 276 | int green = (rgbs[i]>>8) & 0xff; 277 | int red = rgbs[i]&0xff; 278 | rgbs[i] = 0xff000000|(red << 16)|(green << 8)|blue; 279 | } 280 | } else if(type == BufferedImage.TYPE_INT_RGB) { 281 | for(int i = 0; i < rgbs.length; i++) 282 | rgbs[i] = 0xff000000|rgbs[i]; 283 | } else if(type == BufferedImage.TYPE_INT_ARGB) { 284 | ; // Do nothing 285 | } else { 286 | return image.getRGB(0, 0, imageWidth, imageHeight, rgbs, 0, imageWidth); 287 | } 288 | return rgbs; 289 | case DataBuffer.TYPE_BYTE: 290 | byte[] bpixels = (byte[])object; 291 | // BufferedImage.getRGB() seems a bit faster in this case for small images. 292 | if(type == BufferedImage.TYPE_BYTE_INDEXED || type == BufferedImage.TYPE_BYTE_BINARY) { 293 | IndexColorModel indexModel = (IndexColorModel)image.getColorModel(); 294 | int mapSize = indexModel.getMapSize(); 295 | byte[] reds = new byte[mapSize]; 296 | byte[] greens = new byte[mapSize]; 297 | byte[] blues = new byte[mapSize]; 298 | byte[] alphas = new byte[mapSize]; 299 | int[] palette = new int[mapSize]; 300 | indexModel.getReds(reds); 301 | indexModel.getGreens(greens); 302 | indexModel.getBlues(blues); 303 | indexModel.getAlphas(alphas); 304 | for(int i = 0; i < mapSize; i++) 305 | palette[i] = (alphas[i]&0xff)<<24|(reds[i]&0xff)<<16|(greens[i]&0xff)<<8|blues[i]&0xff; 306 | for(int i = 0; i < imageSize; i++) 307 | rgbs[i] = palette[bpixels[i]&0xff]; 308 | } else if(type == BufferedImage.TYPE_4BYTE_ABGR) { 309 | for(int i = 0, index = 0; i < imageSize; i++) 310 | rgbs[i] = (((bpixels[index++]&0xff)<<16)|((bpixels[index++]&0xff)<<8)|(bpixels[index++]&0xff)|((bpixels[index++]&0xff)<<24)); 311 | } else if(type == BufferedImage.TYPE_3BYTE_BGR) { 312 | for(int i = 0, index = 0; i < imageSize; i++) 313 | rgbs[i] = ((0xff000000)|((bpixels[index++]&0xff)<<16)|((bpixels[index++]&0xff)<<8)|(bpixels[index++]&0xff)); 314 | } else if(type == BufferedImage.TYPE_4BYTE_ABGR_PRE) { 315 | for(int i = 0, index = 0; i < imageSize; i++, index += 4) { 316 | float alpha = 255.0f*(bpixels[index+3]&0xff); 317 | byte blue = (byte)((bpixels[index+2]&0xff)*alpha); 318 | byte green = (byte)((bpixels[index+1]&0xff)*alpha); 319 | byte red = (byte)((bpixels[index]&0xff)*alpha); 320 | rgbs[i] = (bpixels[index+3]&0xff000000)|((red&0xff)<<16)|((green&0xff)<<8)|(blue&0xff); 321 | } 322 | } else if(type == BufferedImage.TYPE_BYTE_GRAY) { 323 | for(int i = 0; i < imageSize; i++) 324 | rgbs[i] = (0xff000000)|((bpixels[i]&0xff)<<16)|((bpixels[i]&0xff)<<8)|(bpixels[i]&0xff); 325 | } else { 326 | return image.getRGB(0, 0, imageWidth, imageHeight, rgbs, 0, imageWidth); 327 | } 328 | return rgbs; 329 | case DataBuffer.TYPE_USHORT: 330 | short[] spixels = (short[])object; 331 | if(type == BufferedImage.TYPE_USHORT_GRAY) { 332 | for(int i = 0; i < imageSize; i++) { 333 | int gray = ((spixels[i]>>8)&0xff); 334 | rgbs[i] = (0xff000000)|(gray<<16)|(gray<<8)|gray; 335 | } 336 | } else if(type == BufferedImage.TYPE_USHORT_565_RGB) { 337 | for(int i = 0; i < imageSize; i++) { 338 | int red = ((spixels[i]>>11)&0x1f); 339 | int green = ((spixels[i]>>5)&0x3f); 340 | int blue = (spixels[i]&0x1f); 341 | rgbs[i] = (0xff000000)|(red<<19)|(green<<10)|(blue<<3); 342 | } 343 | } else if(type == BufferedImage.TYPE_USHORT_555_RGB) { 344 | for(int i = 0; i < imageSize; i++) { 345 | int red = ((spixels[i]>>>10)&0x1f); 346 | int green = ((spixels[i]>>>5)&0x1f); 347 | int blue = (spixels[i]&0x1f); 348 | rgbs[i] = (0xff000000)|(red<<19)|(green<<11)|(blue<<3); 349 | } 350 | } else { 351 | return image.getRGB(0, 0, imageWidth, imageHeight, rgbs, 0, imageWidth); 352 | } 353 | return rgbs; 354 | default: 355 | return image.getRGB(0, 0, imageWidth, imageHeight, rgbs, 0, imageWidth); 356 | } 357 | } 358 | 359 | private void init_encoder(int bitsPerPixel ) { 360 | clearCode = 1 << bitsPerPixel; 361 | endOfImage = clearCode + 1; 362 | codeLen = bitsPerPixel + 1; 363 | codeIndex = endOfImage + 1; 364 | // Reset arrays 365 | Arrays.fill(child, 0); 366 | Arrays.fill(siblings, 0); 367 | Arrays.fill(suffix, 0); 368 | } 369 | 370 | /** 371 | * This is intended to be called first when writing an animated GIF 372 | * frame by frame. 373 | * 374 | * @param os OutputStream for the animated GIF 375 | * @param logicalScreenWidth width of the logical screen. If it is less than 376 | * or equal zero, it will be determined from the first frame 377 | * @param logicalScreenHeight height of the logical screen. If it is less than 378 | * or equal zero, it will be determined from the first frame 379 | * @throws Exception 380 | */ 381 | public void prepareForWrite(OutputStream os, int logicalScreenWidth, int logicalScreenHeight) throws Exception { 382 | // Header first 383 | writeHeader(os, true); 384 | this.logicalScreenWidth = logicalScreenWidth; 385 | this.logicalScreenHeight = logicalScreenHeight; 386 | // We are going to write animated GIF, so enable animated flag 387 | animated = true; 388 | } 389 | 390 | // Translate codes into bytes 391 | private void send_code_to_buffer(int code, OutputStream os)throws Exception { 392 | int temp = codeLen; 393 | // Shift the code to the left of the last byte in bytes_buf 394 | bytes_buf[bufIndex] |= ((code&MASK[empty_bits])<<(8-empty_bits)); 395 | code >>= empty_bits; 396 | temp -= empty_bits; 397 | // If the code is longer than the empty_bits 398 | while (temp > 0) 399 | { 400 | if ( ++bufIndex >= 0xff) 401 | { 402 | flush_buf(os,0xff); 403 | } 404 | bytes_buf[bufIndex] |= (code&0xff); 405 | code >>= 8; 406 | temp -= 8; 407 | } 408 | empty_bits = -temp; 409 | } 410 | 411 | public void setLoopCount(int loopCount) { 412 | this.loopCount = loopCount; 413 | } 414 | 415 | private void write(int[] pixels, int imageWidth, int imageHeight, OutputStream os) throws Exception { 416 | // Write GIF header 417 | writeHeader(os, true); 418 | // Set logical screen size 419 | logicalScreenWidth = imageWidth; 420 | logicalScreenHeight = imageHeight; 421 | firstFrame = true; 422 | // We only need to write one frame, so disable animated flag 423 | animated = false; 424 | // Write the image frame 425 | writeFrame(pixels, imageWidth, imageHeight, 0, 0, 0, os); 426 | // Make a clean end up of the image 427 | os.write(IMAGE_TRAILER); 428 | os.close(); 429 | } 430 | 431 | /** 432 | * Writes an array of BufferedImage as an animated GIF 433 | * 434 | * @param images an array of BufferedImage 435 | * @param delays delays in millisecond for each frame 436 | * @param os OutputStream for the animated GIF 437 | * @throws Exception 438 | */ 439 | public void writeAnimatedGIF(BufferedImage[] images, int[] delays, OutputStream os) throws Exception { 440 | // Header first 441 | writeHeader(os, true); 442 | 443 | Dimension logicalScreenSize = getLogicalScreenSize(images); 444 | 445 | logicalScreenWidth = logicalScreenSize.width; 446 | logicalScreenHeight = logicalScreenSize.height; 447 | // We are going to write animated GIF, so enable animated flag 448 | animated = true; 449 | 450 | for(int i = 0; i < images.length; i++) { 451 | // Retrieve image dimension 452 | int imageWidth = images[i].getWidth(); 453 | int imageHeight = images[i].getHeight(); 454 | writeFrame(getRGB(images[i]), imageWidth, imageHeight, 0, 0, delays[i], os); 455 | } 456 | 457 | os.write(IMAGE_TRAILER); 458 | os.close(); 459 | } 460 | 461 | /** 462 | * Writes an array of GIFFrame as an animated GIF 463 | * 464 | * @param frames an array of GIFFrame 465 | * @param os OutputStream for the animated GIF 466 | * @throws Exception 467 | */ 468 | public void writeAnimatedGIF(GIFFrame[] frames, OutputStream os) throws Exception { 469 | // Header first 470 | writeHeader(os, true); 471 | 472 | Dimension logicalScreenSize = getLogicalScreenSize(frames); 473 | 474 | logicalScreenWidth = logicalScreenSize.width; 475 | logicalScreenHeight = logicalScreenSize.height; 476 | // We are going to write animated GIF, so enable animated flag 477 | animated = true; 478 | 479 | for(int i = 0; i < frames.length; i++) { 480 | // Retrieve image dimension 481 | int imageWidth = frames[i].getFrameWidth(); 482 | int imageHeight = frames[i].getFrameHeight(); 483 | int[] pixels = getRGB(frames[i].getFrame()); 484 | if(frames[i].getTransparencyFlag() == GIFFrame.TRANSPARENCY_INDEX_SET && frames[i].getTransparentColor() != -1) { 485 | int transColor = (frames[i].getTransparentColor() & 0x00ffffff); 486 | for(int j = pixels.length - 1; j > 0; j--) { 487 | int pixel = (pixels[j] & 0x00ffffff); 488 | if(pixel == transColor) pixels[j] = pixel; 489 | } 490 | } 491 | writeFrame(pixels, imageWidth, imageHeight, frames[i].getLeftPosition(), frames[i].getTopPosition(), 492 | frames[i].getDelay(), frames[i].getDisposalMethod(), frames[i].getUserInputFlag(), os); 493 | } 494 | 495 | os.write(IMAGE_TRAILER); 496 | os.close(); 497 | } 498 | 499 | /** 500 | * Writes a list of GIFFrame as an animated GIF 501 | * 502 | * @param frames a list of GIFFrame 503 | * @param os OutputStream for the animated GIF 504 | * @throws Exception 505 | */ 506 | public void writeAnimatedGIF(List frames, OutputStream os) throws Exception { 507 | writeAnimatedGIF(frames.toArray(new GIFFrame[0]), os); 508 | } 509 | 510 | private void writeComment(OutputStream os, String comment) throws Exception { 511 | os.write(EXTENSION_INTRODUCER); 512 | os.write(COMMENT_EXTENSION_LABEL); 513 | byte[] commentBytes = comment.getBytes(); 514 | int numBlocks = commentBytes.length/0xff; 515 | int leftOver = commentBytes.length % 0xff; 516 | int offset = 0; 517 | if(numBlocks > 0) { 518 | for(int i = 0; i < numBlocks; i++) { 519 | os.write(0xff); 520 | os.write(commentBytes, offset, 0xff); 521 | offset += 0xff; 522 | } 523 | } 524 | if(leftOver > 0) { 525 | os.write(leftOver); 526 | os.write(commentBytes, offset, leftOver); 527 | } 528 | os.write(0); 529 | } 530 | 531 | public void writeFrame(OutputStream os, GIFFrame frame) throws Exception { 532 | // Retrieve image dimension 533 | BufferedImage image = frame.getFrame(); 534 | int imageWidth = image.getWidth(); 535 | int imageHeight = image.getHeight(); 536 | int frameLeft = frame.getLeftPosition(); 537 | int frameTop = frame.getTopPosition(); 538 | // Determine the logical screen dimension 539 | if(firstFrame) { 540 | if(logicalScreenWidth <= 0) 541 | logicalScreenWidth = imageWidth; 542 | if(logicalScreenHeight <= 0) 543 | logicalScreenHeight = imageHeight; 544 | } 545 | if(frameLeft >= logicalScreenWidth || frameTop >= logicalScreenHeight) return; 546 | if((frameLeft + imageWidth) > logicalScreenWidth) imageWidth = logicalScreenWidth - frameLeft; 547 | if((frameTop + imageHeight) > logicalScreenHeight) imageHeight = logicalScreenHeight - frameTop; 548 | int[] pixels = getRGB(image.getSubimage(0, 0, imageWidth, imageHeight)); 549 | // Handle transparency color if explicitly set 550 | if(frame.getTransparencyFlag() == GIFFrame.TRANSPARENCY_INDEX_SET && frame.getTransparentColor() != -1) { 551 | int transColor = (frame.getTransparentColor() & 0x00ffffff); 552 | for(int j = pixels.length - 1; j > 0; j--) { 553 | int pixel = (pixels[j] & 0x00ffffff); 554 | if(pixel == transColor) pixels[j] = pixel; 555 | } 556 | } 557 | writeFrame(pixels, imageWidth, imageHeight, frame.getLeftPosition(), frame.getTopPosition(), 558 | frame.getDelay(), frame.getDisposalMethod(), frame.getUserInputFlag(), os); 559 | } 560 | 561 | public void writeFrame(OutputStream os, BufferedImage frame) throws Exception { 562 | writeFrame(os, frame, 100); // default delay is 100 milliseconds 563 | } 564 | 565 | public void writeFrame(OutputStream os, BufferedImage frame, int delay) throws Exception { 566 | // Retrieve image dimension 567 | int imageWidth = frame.getWidth(); 568 | int imageHeight = frame.getHeight(); 569 | // Determine the logical screen dimension 570 | if(firstFrame) { 571 | if(logicalScreenWidth <= 0) 572 | logicalScreenWidth = imageWidth; 573 | if(logicalScreenHeight <= 0) 574 | logicalScreenHeight = imageHeight; 575 | } 576 | if(delay <= 0) delay = 100; 577 | if(imageWidth > logicalScreenWidth) imageWidth = logicalScreenWidth; 578 | if(imageHeight > logicalScreenHeight) imageHeight = logicalScreenHeight; 579 | writeFrame(getRGB(frame.getSubimage(0, 0, imageWidth, imageHeight)), imageWidth, imageHeight, 0, 0, delay, os); 580 | } 581 | 582 | private void writeFrame(int[] pixels, int imageWidth, int imageHeight, int imageLeftPosition, int imageTopPosition, int delay, int disposalMethod, int userInputFlag, OutputStream os) throws Exception { 583 | // Reset empty_bits 584 | empty_bits = 0x08; 585 | 586 | int transparent_color = -1; 587 | int[] colorInfo; 588 | 589 | // Reduce colors, if the color depth is less than 8 bits, reduce colors 590 | // to the actual bits needed, otherwise reduce to 8 bits. 591 | byte[] newPixels = new byte[imageWidth*imageHeight]; 592 | colorPalette = new int[256]; 593 | 594 | colorInfo = checkColorDepth(pixels, newPixels, colorPalette); 595 | 596 | if(colorInfo[0] > 0x08) { 597 | bitsPerPixel = 8; 598 | if(isApplyDither) 599 | colorInfo = reduceColorsDiffusionDither(pixels, imageWidth, imageHeight, bitsPerPixel, newPixels, colorPalette); 600 | else 601 | colorInfo = reduceColors(pixels, bitsPerPixel, newPixels, colorPalette); 602 | } 603 | 604 | bitsPerPixel = colorInfo[0]; 605 | 606 | transparent_color = colorInfo[1]; 607 | 608 | int num_of_color = 1<= 0) 619 | bgcolor = (byte)transparent_color; 620 | // Write logical screen descriptor 621 | writeLSD(os, (short)logicalScreenWidth, (short)logicalScreenHeight, flags, bgcolor, aspectRatio); 622 | // Write the global colorPalette 623 | writePalette(os, num_of_color); 624 | writeComment(os, "Created by ICAFE - https://github.com/dragon66/icafe"); 625 | if(animated)// Write Netscape extension block 626 | writeNetscapeApplicationBlock(os, loopCount); 627 | } 628 | 629 | // Output the graphic control block 630 | writeGraphicControlBlock(os, delay, transparent_color, disposalMethod, userInputFlag); 631 | // Output image descriptor 632 | if(firstFrame) { 633 | writeImageDescriptor(os, imageWidth, imageHeight, imageLeftPosition, imageTopPosition, -1); 634 | firstFrame = false; 635 | } else { 636 | writeImageDescriptor(os, imageWidth, imageHeight, imageLeftPosition, imageTopPosition, bitsPerPixel - 1); 637 | // Write local colorPalette 638 | writePalette(os, num_of_color); 639 | } 640 | // LZW encode the image 641 | encode(newPixels, os); 642 | /** Write out a zero length data sub-block */ 643 | os.write(0x00); 644 | } 645 | 646 | private void writeFrame(int[] pixels, int imageWidth, int imageHeight, int imageLeftPosition, int imageTopPosition, int delay, OutputStream os) throws Exception { 647 | writeFrame(pixels, imageWidth, imageHeight, imageLeftPosition, imageTopPosition, delay, GIFFrame.DISPOSAL_RESTORE_TO_BACKGROUND, GIFFrame.USER_INPUT_NONE, os); 648 | } 649 | 650 | // Unit of delay is supposed to be in millisecond 651 | private void writeGraphicControlBlock(OutputStream os, int delay, int transparent_color, int disposalMethod, int userInputFlag) throws Exception { 652 | // Scale delay 653 | delay = Math.round(delay/10.0f); 654 | 655 | byte[] buf = new byte[8]; 656 | buf[0] = EXTENSION_INTRODUCER; // Extension introducer 657 | buf[1] = GRAPHIC_CONTROL_LABEL; // Graphic control label 658 | buf[2] = 0x04; // Block size 659 | // Add disposalMethod and userInputFlag 660 | buf[3] |= (((disposalMethod&0x07) << 2)|((userInputFlag&0x01) << 1)); 661 | buf[4] = (byte)(delay&0xff);// Delay time 662 | buf[5] = (byte)((delay>>8)&0xff); 663 | buf[6] = (byte)transparent_color; 664 | buf[7] = 0x00; 665 | 666 | if(transparent_color >= 0) // Add transparency indicator 667 | buf[3] |= 0x01; 668 | 669 | os.write(buf, 0, 8); 670 | } 671 | 672 | private void writeHeader(OutputStream os, boolean newFormat) throws IOException { 673 | // 6 bytes: GIF signature (always "GIF") plus GIF version ("87a" or "89a") 674 | if(newFormat) 675 | os.write("GIF89a".getBytes()); 676 | else 677 | os.write("GIF87a".getBytes()); 678 | } 679 | 680 | private void writeImageDescriptor(OutputStream os, int imageWidth, int imageHeight, int imageLeftPosition, int imageTopPosition, int colorTableSize) throws Exception { 681 | byte imageDescriptor[] = new byte[10]; 682 | imageDescriptor[0] = IMAGE_SEPARATOR;// Image separator "," 683 | imageDescriptor[1] = (byte)(imageLeftPosition&0xff);// Image left position 684 | imageDescriptor[2] = (byte)((imageLeftPosition>>8)&0xff); 685 | imageDescriptor[3] = (byte)(imageTopPosition&0xff);// Image top position 686 | imageDescriptor[4] = (byte)((imageTopPosition>>8)&0xff); 687 | imageDescriptor[5] = (byte)(imageWidth&0xff); 688 | imageDescriptor[6] = (byte)((imageWidth>>8)&0xff); 689 | imageDescriptor[7] = (byte)(imageHeight&0xff); 690 | imageDescriptor[8] = (byte)((imageHeight>>8)&0xff); 691 | imageDescriptor[9] = (byte)0x20;//0b00100000 - Packed fields 692 | 693 | if(colorTableSize >= 0) // Local color table will follow 694 | imageDescriptor[9] |= (1<<7|colorTableSize); 695 | 696 | os.write(imageDescriptor, 0, 10); 697 | } 698 | 699 | // Write logical screen descriptor 700 | private void writeLSD(OutputStream os, short screen_width, short screen_height, short flags, byte bgcolor, byte aspectRatio) throws IOException { 701 | byte[] descriptor = new byte[7]; 702 | // Screen_width 703 | descriptor[0] = (byte)(screen_width&0xff); 704 | descriptor[1] = (byte)((screen_width>>8)&0xff); 705 | // Screen_height 706 | descriptor[2] = (byte)(screen_height&0xff); 707 | descriptor[3] = (byte)((screen_height>>8)&0xff); 708 | // Global flags 709 | descriptor[4] = (byte)(flags&0xff); 710 | // Background color 711 | descriptor[5] = bgcolor; 712 | // AspectRatio 713 | descriptor[6] = aspectRatio; 714 | 715 | os.write(descriptor); 716 | } 717 | 718 | private void writeNetscapeApplicationBlock(OutputStream os, int loopCounts) throws Exception { 719 | byte[] buf = new byte[19]; 720 | buf[0] = EXTENSION_INTRODUCER; // Extension introducer 721 | buf[1] = APPLICATION_EXTENSION_LABEL; // Application extension label 722 | buf[2] = 0x0b; // Block size 723 | buf[3] = 'N'; // Application Identifier (8 bytes) 724 | buf[4] = 'E'; 725 | buf[5] = 'T'; 726 | buf[6] = 'S'; 727 | buf[7] = 'C'; 728 | buf[8] = 'A'; 729 | buf[9] = 'P'; 730 | buf[10]= 'E'; 731 | buf[11]= '2';// Application Authentication Code (3 bytes) 732 | buf[12]= '.'; 733 | buf[13]= '0'; 734 | buf[14]= 0x03; 735 | buf[15]= 0x01; 736 | buf[16]= (byte)(loopCounts&0xff); // Loop counts 737 | buf[17]= (byte)((loopCounts>>8)&0xff); 738 | buf[18]= 0x00; // Block terminator 739 | 740 | os.write(buf); 741 | } 742 | 743 | private void writePalette(OutputStream os, int num_of_color) throws Exception { 744 | int index = 0; 745 | byte colors[] = new byte[num_of_color*3]; 746 | 747 | for (int i=0; i>16)&0xff)); 750 | colors[index++] = (byte)(((colorPalette[i]>>8)&0xff)); 751 | colors[index++] = (byte)(colorPalette[i]&0xff); 752 | } 753 | 754 | os.write(colors, 0, num_of_color*3); 755 | } 756 | 757 | private static int[] checkColorDepth(int[] rgbTriplets, byte[] newPixels, final int[] colorPalette) { 758 | int index = 0; 759 | int temp = 0; 760 | int bitsPerPixel = 1; 761 | int transparent_index = -1;// Transparent color index 762 | int transparent_color = -1;// Transparent color 763 | int[] colorInfo = new int[2];// Return value 764 | 765 | IntHashtable rgbHash = new IntHashtable(1023); 766 | 767 | for (int i = 0; i < rgbTriplets.length; i++) { 768 | temp = (rgbTriplets[i]&0x00ffffff); 769 | 770 | if((rgbTriplets[i] >>> 24) < 0x80 ) { // Transparent 771 | if (transparent_index < 0) { 772 | transparent_index = index; 773 | transparent_color = temp;// Remember transparent color 774 | } 775 | temp = Integer.MAX_VALUE; 776 | } 777 | 778 | Integer entry = rgbHash.get(temp); 779 | 780 | if (entry!=null) { 781 | newPixels[i] = entry.byteValue(); 782 | } else { 783 | if(index > 0xff) {// More than 256 colors, have to reduce 784 | // Colors before saving as an indexed color image 785 | colorInfo[0] = 24; 786 | return colorInfo; 787 | } 788 | rgbHash.put(temp, index); 789 | newPixels[i] = (byte)index; 790 | colorPalette[index++] = ((0xff<<24)|temp); 791 | } 792 | } 793 | if(transparent_index >= 0)// This line could be used to set a different background color 794 | colorPalette[transparent_index] = transparent_color; 795 | // Return the actual bits per pixel and the transparent color index if any 796 | while ((1< 8 || colorDepth < 1) 806 | throw new IllegalArgumentException("Invalid color depth " + colorDepth); 807 | int[] colorInfo = new int[2]; 808 | int colors = 0; 809 | colors = new WuQuant(rgbTriplets, 1<>> 24) < 0x80 ) { 849 | newPixels[index1] = (byte)transparent_index; 850 | continue; 851 | } 852 | 853 | red = ((rgbTriplet[index1]&0xff0000)>>>16) + thisErrR[col + 1]; 854 | if (red > 255) red = 255; 855 | else if (red < 0) red = 0; 856 | 857 | green = ((rgbTriplet[index1]&0x00ff00)>>>8) + thisErrG[col + 1]; 858 | if (green > 255) green = 255; 859 | else if (green < 0) green = 0; 860 | 861 | blue = (rgbTriplet[index1]&0x0000ff) + thisErrB[col + 1]; 862 | if (blue > 255) blue = 255; 863 | else if (blue < 0) blue = 0; 864 | 865 | // Find the nearest color index 866 | index = invMap.getNearestColorIndex(red, green, blue); 867 | newPixels[index1] = (byte)index;// The colorPalette index for this pixel 868 | 869 | // Find errors for different channels 870 | err1 = red - ((colorPalette[index]>>16)&0xff);// Red channel 871 | err2 = green - ((colorPalette[index]>>8)&0xff);// Green channel 872 | err3 = blue - (colorPalette[index]&0xff);// Blue channel 873 | // Diffuse error 874 | // Red 875 | thisErrR[col + 2] += ((err1*7)/16); 876 | nextErrR[col ] += ((err1*3)/16); 877 | nextErrR[col + 1] += ((err1*5)/16); 878 | nextErrR[col + 2] += ((err1)/16); 879 | // Green 880 | thisErrG[col + 2] += ((err2*7)/16); 881 | nextErrG[col ] += ((err2*3)/16); 882 | nextErrG[col + 1] += ((err2*5)/16); 883 | nextErrG[col + 2] += ((err2)/16); 884 | // Blue 885 | thisErrB[col + 2] += ((err3*7)/16); 886 | nextErrB[col ] += ((err3*3)/16); 887 | nextErrB[col + 1] += ((err3*5)/16); 888 | nextErrB[col + 2] += ((err3)/16); 889 | } 890 | // We have finished one row, switch the error arrays 891 | tempErr = thisErrR; 892 | thisErrR = nextErrR; 893 | nextErrR = tempErr; 894 | 895 | tempErr = thisErrG; 896 | thisErrG = nextErrG; 897 | nextErrG = tempErr; 898 | 899 | tempErr = thisErrB; 900 | thisErrB = nextErrB; 901 | nextErrB = tempErr; 902 | 903 | // Clear the error arrays 904 | Arrays.fill(nextErrR, 0); 905 | Arrays.fill(nextErrG, 0); 906 | Arrays.fill(nextErrB, 0); 907 | } 908 | } 909 | 910 | public static class GIFFrame { 911 | // Frame parameters 912 | private BufferedImage frame; 913 | private int leftPosition; 914 | private int topPosition; 915 | private int frameWidth; 916 | private int frameHeight; 917 | private int delay; 918 | private int disposalMethod = DISPOSAL_UNSPECIFIED; 919 | private int userInputFlag = USER_INPUT_NONE; 920 | private int transparencyFlag = TRANSPARENCY_INDEX_NONE; 921 | 922 | // The transparent color value in RRGGBB format. 923 | // The highest order byte has no effect. 924 | private int transparentColor = TRANSPARENCY_COLOR_NONE; // Default no transparent color 925 | 926 | public static final int DISPOSAL_UNSPECIFIED = 0; 927 | public static final int DISPOSAL_LEAVE_AS_IS = 1; 928 | public static final int DISPOSAL_RESTORE_TO_BACKGROUND = 2; 929 | public static final int DISPOSAL_RESTORE_TO_PREVIOUS = 3; 930 | // Values between 4-7 inclusive 931 | public static final int DISPOSAL_TO_BE_DEFINED = 7; 932 | 933 | public static final int USER_INPUT_NONE = 0; 934 | public static final int USER_INPUT_EXPECTED = 1; 935 | 936 | public static final int TRANSPARENCY_INDEX_NONE = 0; 937 | public static final int TRANSPARENCY_INDEX_SET = 1; 938 | 939 | public static final int TRANSPARENCY_COLOR_NONE = -1; 940 | 941 | public GIFFrame(BufferedImage frame) { 942 | this(frame, 0, 0, 0, GIFFrame.DISPOSAL_UNSPECIFIED); 943 | } 944 | 945 | public GIFFrame(BufferedImage frame, int delay) { 946 | this(frame, 0, 0, delay, GIFFrame.DISPOSAL_UNSPECIFIED); 947 | } 948 | 949 | public GIFFrame(BufferedImage frame, int delay, int disposalMethod) { 950 | this(frame, 0, 0, delay, disposalMethod); 951 | } 952 | 953 | public GIFFrame(BufferedImage frame, int leftPosition, int topPosition, int delay, int disposalMethod) { 954 | this(frame, leftPosition, topPosition, delay, disposalMethod, USER_INPUT_NONE, TRANSPARENCY_INDEX_NONE, TRANSPARENCY_COLOR_NONE); 955 | } 956 | 957 | public GIFFrame(BufferedImage frame, int leftPosition, int topPosition, int delay, int disposalMethod, int userInputFlag, int transparencyFlag, int transparentColor) { 958 | if(frame == null) throw new IllegalArgumentException("Null input image"); 959 | if(disposalMethod < DISPOSAL_UNSPECIFIED || disposalMethod > DISPOSAL_TO_BE_DEFINED) 960 | throw new IllegalArgumentException("Invalid disposal method: " + disposalMethod); 961 | if(userInputFlag < USER_INPUT_NONE || userInputFlag > USER_INPUT_EXPECTED) 962 | throw new IllegalArgumentException("Invalid user input flag: " + userInputFlag); 963 | if(transparencyFlag < TRANSPARENCY_INDEX_NONE || transparencyFlag > TRANSPARENCY_INDEX_SET) 964 | throw new IllegalArgumentException("Invalid transparency flag: " + transparencyFlag); 965 | if(leftPosition < 0 || topPosition < 0) 966 | throw new IllegalArgumentException("Negative coordinates for frame top-left position"); 967 | if(delay < 0) delay = 0; 968 | this.frame = frame; 969 | this.leftPosition = leftPosition; 970 | this.topPosition = topPosition; 971 | this.delay = delay; 972 | this.disposalMethod = disposalMethod; 973 | this.userInputFlag = userInputFlag; 974 | this.transparencyFlag = transparencyFlag; 975 | this.frameWidth = frame.getWidth(); 976 | this.frameHeight = frame.getHeight(); 977 | this.transparentColor = transparentColor; 978 | } 979 | 980 | public int getDelay() { 981 | return delay; 982 | } 983 | 984 | public int getDisposalMethod() { 985 | return disposalMethod; 986 | } 987 | 988 | public BufferedImage getFrame() { 989 | return frame; 990 | } 991 | 992 | public int getFrameHeight() { 993 | return frameHeight; 994 | } 995 | 996 | public int getFrameWidth() { 997 | return frameWidth; 998 | } 999 | 1000 | public int getLeftPosition() { 1001 | return leftPosition; 1002 | } 1003 | 1004 | public int getTopPosition() { 1005 | return topPosition; 1006 | } 1007 | 1008 | public int getTransparentColor() { 1009 | return transparentColor; 1010 | } 1011 | 1012 | public int getTransparencyFlag() { 1013 | return transparencyFlag; 1014 | } 1015 | 1016 | public int getUserInputFlag() { 1017 | return userInputFlag; 1018 | } 1019 | } 1020 | 1021 | /** 1022 | * Java port of 1023 | * C Implementation of Wu's Color Quantizer (v. 2) 1024 | * (see Graphics Gems vol. II, pp. 126-133) 1025 | * Author: Xiaolin Wu 1026 | * Dept. of Computer Science 1027 | * Univ. of Western Ontario 1028 | * London, Ontario N6A 5B7 1029 | * wu@csd.uwo.ca 1030 | * 1031 | * Algorithm: Greedy orthogonal bipartition of RGB space for variance 1032 | * minimization aided by inclusion-exclusion tricks. 1033 | * For speed no nearest neighbor search is done. Slightly 1034 | * better performance can be expected by more sophisticated 1035 | * but more expensive versions. 1036 | * 1037 | * The author thanks Tom Lane at Tom_Lane@G.GP.CS.CMU.EDU for much of 1038 | * additional documentation and a cure to a previous bug. 1039 | * 1040 | * Free to distribute, comments and suggestions are appreciated. 1041 | */ 1042 | private static class WuQuant { 1043 | private static final int MAXCOLOR = 256; 1044 | private static final int RED = 2; 1045 | private static final int GREEN = 1; 1046 | private static final int BLUE = 0; 1047 | 1048 | private static int QUANT_SIZE = 33;// quant size 1049 | 1050 | private static final class Box { 1051 | int r0; /* min value, exclusive */ 1052 | int r1; /* max value, inclusive */ 1053 | int g0; 1054 | int g1; 1055 | int b0; 1056 | int b1; 1057 | int vol; 1058 | }; 1059 | 1060 | private int size; /*image size*/ 1061 | private int lut_size; /*color look-up table size*/ 1062 | private int qadd[]; 1063 | private int pixels[]; 1064 | private int transparent_color = -1;// Transparent color 1065 | 1066 | private float m2[][][] = new float[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; 1067 | private long wt[][][] = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; 1068 | private long mr[][][] = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; 1069 | private long mg[][][] = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; 1070 | private long mb[][][] = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; 1071 | 1072 | public WuQuant(int[] pixels, int lut_size) { 1073 | this.pixels = pixels; 1074 | this.size = pixels.length; 1075 | this.lut_size = lut_size; 1076 | } 1077 | 1078 | public int quantize(final byte[] newPixels, final int[] lut, int[] colorInfo) { 1079 | Box cube[] = new Box[MAXCOLOR]; 1080 | int lut_r, lut_g, lut_b; 1081 | int tag[] = new int[QUANT_SIZE*QUANT_SIZE*QUANT_SIZE]; 1082 | 1083 | int next, i, k; 1084 | long weight; 1085 | float vv[] = new float[MAXCOLOR], temp; 1086 | 1087 | Hist3d(wt, mr, mg, mb, m2); 1088 | M3d(wt, mr, mg, mb, m2); 1089 | 1090 | for(i = 0; i < MAXCOLOR; i++) 1091 | cube[i] = new Box(); 1092 | 1093 | cube[0].r0 = cube[0].g0 = cube[0].b0 = 0; 1094 | cube[0].r1 = cube[0].g1 = cube[0].b1 = QUANT_SIZE - 1; 1095 | next = 0; 1096 | 1097 | if(transparent_color >= 0) lut_size--; 1098 | 1099 | for(i = 1; i < lut_size; ++i){ 1100 | if (Cut(cube[next], cube[i])) { 1101 | /* volume test ensures we won't try to cut one-cell box */ 1102 | vv[next] = (cube[next].vol > 1) ? Var(cube[next]) : 0.0f; 1103 | vv[i] = (cube[i].vol > 1) ? Var(cube[i]) : 0.0f; 1104 | } else { 1105 | vv[next] = 0.0f; /* don't try to split this box again */ 1106 | i--; /* didn't create box i */ 1107 | } 1108 | next = 0; temp = vv[0]; 1109 | for(k = 1; k <= i; ++k) 1110 | if (vv[k] > temp) { 1111 | temp = vv[k]; next = k; 1112 | } 1113 | if (temp <= 0.0f) { 1114 | k = i + 1; 1115 | break; 1116 | } 1117 | } 1118 | 1119 | for(k = 0; k < lut_size; ++k){ 1120 | Mark(cube[k], k, tag); 1121 | weight = Vol(cube[k], wt); 1122 | if (weight > 0) { 1123 | lut_r = (int)(Vol(cube[k], mr) / weight); 1124 | lut_g = (int)(Vol(cube[k], mg) / weight); 1125 | lut_b = (int)(Vol(cube[k], mb) / weight); 1126 | lut[k] = (255 << 24) | (lut_r << 16) | (lut_g << 8) | lut_b; 1127 | } 1128 | else { 1129 | lut[k] = 0; 1130 | } 1131 | } 1132 | 1133 | for(i = 0; i < size; ++i) { 1134 | if((pixels[i] >>> 24) < 0x80) 1135 | newPixels[i] = (byte)lut_size; 1136 | else 1137 | newPixels[i] = (byte)tag[qadd[i]]; 1138 | } 1139 | 1140 | int bitsPerPixel = 0; 1141 | while ((1<= 0) { 1146 | lut[lut_size] = transparent_color; // Set the transparent color 1147 | colorInfo[1] = lut_size; 1148 | } 1149 | 1150 | return lut_size; 1151 | } 1152 | 1153 | public int quantize(final int[] lut, int[] colorInfo) { 1154 | Box cube[] = new Box[MAXCOLOR]; 1155 | int lut_r, lut_g, lut_b; 1156 | 1157 | int next, i, k; 1158 | long weight; 1159 | float vv[] = new float[MAXCOLOR], temp; 1160 | 1161 | Hist3d(wt, mr, mg, mb, m2); 1162 | M3d(wt, mr, mg, mb, m2); 1163 | 1164 | for(i = 0; i < MAXCOLOR; i++) 1165 | cube[i] = new Box(); 1166 | 1167 | cube[0].r0 = cube[0].g0 = cube[0].b0 = 0; 1168 | cube[0].r1 = cube[0].g1 = cube[0].b1 = QUANT_SIZE - 1; 1169 | next = 0; 1170 | 1171 | if(transparent_color >= 0) lut_size--; 1172 | 1173 | for(i = 1; i < lut_size; ++i){ 1174 | if (Cut(cube[next], cube[i])) { 1175 | /* volume test ensures we won't try to cut one-cell box */ 1176 | vv[next] = (cube[next].vol > 1) ? Var(cube[next]) : 0.0f; 1177 | vv[i] = (cube[i].vol > 1) ? Var(cube[i]) : 0.0f; 1178 | } else { 1179 | vv[next] = 0.0f; /* don't try to split this box again */ 1180 | i--; /* didn't create box i */ 1181 | } 1182 | next = 0; temp = vv[0]; 1183 | for(k = 1; k <= i; ++k) 1184 | if (vv[k] > temp) { 1185 | temp = vv[k]; next = k; 1186 | } 1187 | if (temp <= 0.0f) { 1188 | k = i + 1; 1189 | break; 1190 | } 1191 | } 1192 | 1193 | for(k = 0; k < lut_size; ++k){ 1194 | weight = Vol(cube[k], wt); 1195 | if (weight > 0) { 1196 | lut_r = (int)(Vol(cube[k], mr) / weight); 1197 | lut_g = (int)(Vol(cube[k], mg) / weight); 1198 | lut_b = (int)(Vol(cube[k], mb) / weight); 1199 | lut[k] = (255 << 24) | (lut_r << 16) | (lut_g << 8) | lut_b; 1200 | } 1201 | else { 1202 | lut[k] = 0; 1203 | } 1204 | } 1205 | 1206 | int bitsPerPixel = 0; 1207 | while ((1<= 0) { 1212 | lut[lut_size] = transparent_color; // Set the transparent color 1213 | colorInfo[1] = lut_size; 1214 | } 1215 | 1216 | return lut_size; 1217 | } 1218 | 1219 | /* Histogram is in elements 1..HISTSIZE along each axis, 1220 | * element 0 is for base or marginal value 1221 | * NB: these must start out 0! 1222 | */ 1223 | private void Hist3d(long vwt[][][], long vmr[][][], long vmg[][][], long vmb[][][], float m2[][][]) { 1224 | /* build 3-D color histogram of counts, r/g/b, c^2 */ 1225 | int r, g, b; 1226 | int i, inr, ing, inb, table[] = new int[256]; 1227 | 1228 | for(i = 0; i < 256; ++i) table[i]= i*i; 1229 | 1230 | qadd = new int[size]; 1231 | 1232 | for(i = 0; i < size; ++i) { 1233 | int rgb = pixels[i]; 1234 | if((rgb >>> 24) < 0x80) { // Transparent 1235 | if (transparent_color < 0) // Find the transparent color 1236 | transparent_color = rgb; 1237 | } 1238 | r = ((rgb >> 16)& 0xff); 1239 | g = ((rgb >> 8 )& 0xff); 1240 | b = ( rgb & 0xff); 1241 | inr = (r >> 3) + 1; 1242 | ing = (g >> 3) + 1; 1243 | inb = (b >> 3) + 1; 1244 | qadd[i] = (inr << 10) + (inr << 6) + inr + (ing << 5) + ing + inb; 1245 | /*[inr][ing][inb]*/ 1246 | ++vwt[inr][ing][inb]; 1247 | vmr[inr][ing][inb] += r; 1248 | vmg[inr][ing][inb] += g; 1249 | vmb[inr][ing][inb] += b; 1250 | m2[inr][ing][inb] += table[r] + table[g] + table[b]; 1251 | } 1252 | } 1253 | 1254 | /* At conclusion of the histogram step, we can interpret 1255 | * wt[r][g][b] = sum over voxel of P(c) 1256 | * mr[r][g][b] = sum over voxel of r*P(c) , similarly for mg, mb 1257 | * m2[r][g][b] = sum over voxel of c^2*P(c) 1258 | * Actually each of these should be divided by 'size' to give the usual 1259 | * interpretation of P() as ranging from 0 to 1, but we needn't do that here. 1260 | */ 1261 | 1262 | /* We now convert histogram into moments so that we can rapidly calculate 1263 | * the sums of the above quantities over any desired box. 1264 | */ 1265 | private void M3d(long vwt[][][], long vmr[][][], long vmg[][][], long vmb[][][], float m2[][][]) { 1266 | /* compute cumulative moments. */ 1267 | int i, r, g, b; 1268 | int line, line_r, line_g, line_b; 1269 | int area[] = new int[QUANT_SIZE]; 1270 | int area_r[] = new int[QUANT_SIZE]; 1271 | int area_g[] = new int[QUANT_SIZE]; 1272 | int area_b[] = new int[QUANT_SIZE]; 1273 | float line2, area2[] = new float[QUANT_SIZE]; 1274 | 1275 | for(r = 1; r < QUANT_SIZE; ++r) { 1276 | for(i = 0; i < QUANT_SIZE; ++i) 1277 | area2[i] = area[i] = area_r[i] = area_g[i] = area_b[i] = 0; 1278 | for(g = 1; g < QUANT_SIZE; ++g) { 1279 | line2 = line = line_r = line_g = line_b = 0; 1280 | for(b = 1; b < QUANT_SIZE; ++b){ 1281 | line += vwt[r][g][b]; 1282 | line_r += vmr[r][g][b]; 1283 | line_g += vmg[r][g][b]; 1284 | line_b += vmb[r][g][b]; 1285 | line2 += m2[r][g][b]; 1286 | 1287 | area[b] += line; 1288 | area_r[b] += line_r; 1289 | area_g[b] += line_g; 1290 | area_b[b] += line_b; 1291 | area2[b] += line2; 1292 | 1293 | vwt[r][g][b] = vwt[r-1][g][b] + area[b]; 1294 | vmr[r][g][b] = vmr[r-1][g][b] + area_r[b]; 1295 | vmg[r][g][b] = vmg[r-1][g][b] + area_g[b]; 1296 | vmb[r][g][b] = vmb[r-1][g][b] + area_b[b]; 1297 | m2[r][g][b] = m2[r-1][g][b] + area2[b]; 1298 | } 1299 | } 1300 | } 1301 | } 1302 | 1303 | private long Vol(Box cube, long mmt[][][]) { 1304 | /* Compute sum over a box of any given statistic */ 1305 | return ( mmt[cube.r1][cube.g1][cube.b1] 1306 | -mmt[cube.r1][cube.g1][cube.b0] 1307 | -mmt[cube.r1][cube.g0][cube.b1] 1308 | +mmt[cube.r1][cube.g0][cube.b0] 1309 | -mmt[cube.r0][cube.g1][cube.b1] 1310 | +mmt[cube.r0][cube.g1][cube.b0] 1311 | +mmt[cube.r0][cube.g0][cube.b1] 1312 | -mmt[cube.r0][cube.g0][cube.b0] ); 1313 | } 1314 | 1315 | /* The next two routines allow a slightly more efficient calculation 1316 | * of Vol() for a proposed subbox of a given box. The sum of Top() 1317 | * and Bottom() is the Vol() of a subbox split in the given direction 1318 | * and with the specified new upper bound. 1319 | */ 1320 | 1321 | private long Bottom(Box cube, int dir, long mmt[][][]) { 1322 | /* Compute part of Vol(cube, mmt) that doesn't depend on r1, g1, or b1 */ 1323 | /* (depending on dir) */ 1324 | switch(dir) { 1325 | case RED: 1326 | return( -mmt[cube.r0][cube.g1][cube.b1] 1327 | +mmt[cube.r0][cube.g1][cube.b0] 1328 | +mmt[cube.r0][cube.g0][cube.b1] 1329 | -mmt[cube.r0][cube.g0][cube.b0] ); 1330 | case GREEN: 1331 | return( -mmt[cube.r1][cube.g0][cube.b1] 1332 | +mmt[cube.r1][cube.g0][cube.b0] 1333 | +mmt[cube.r0][cube.g0][cube.b1] 1334 | -mmt[cube.r0][cube.g0][cube.b0] ); 1335 | case BLUE: 1336 | return( -mmt[cube.r1][cube.g1][cube.b0] 1337 | +mmt[cube.r1][cube.g0][cube.b0] 1338 | +mmt[cube.r0][cube.g1][cube.b0] 1339 | -mmt[cube.r0][cube.g0][cube.b0] ); 1340 | default: 1341 | return 0; 1342 | } 1343 | } 1344 | 1345 | private long Top(Box cube, int dir, int pos, long mmt[][][]) { 1346 | /* Compute remainder of Vol(cube, mmt), substituting pos for */ 1347 | /* r1, g1, or b1 (depending on dir) */ 1348 | switch(dir) { 1349 | case RED: 1350 | return( mmt[pos][cube.g1][cube.b1] 1351 | -mmt[pos][cube.g1][cube.b0] 1352 | -mmt[pos][cube.g0][cube.b1] 1353 | +mmt[pos][cube.g0][cube.b0] ); 1354 | case GREEN: 1355 | return( mmt[cube.r1][pos][cube.b1] 1356 | -mmt[cube.r1][pos][cube.b0] 1357 | -mmt[cube.r0][pos][cube.b1] 1358 | +mmt[cube.r0][pos][cube.b0] ); 1359 | case BLUE: 1360 | return( mmt[cube.r1][cube.g1][pos] 1361 | -mmt[cube.r1][cube.g0][pos] 1362 | -mmt[cube.r0][cube.g1][pos] 1363 | +mmt[cube.r0][cube.g0][pos] ); 1364 | default: 1365 | return 0; 1366 | } 1367 | } 1368 | 1369 | private float Var(Box cube) { 1370 | /* Compute the weighted variance of a box */ 1371 | /* NB: as with the raw statistics, this is really the variance * size */ 1372 | float dr, dg, db, xx; 1373 | dr = Vol(cube, mr); 1374 | dg = Vol(cube, mg); 1375 | db = Vol(cube, mb); 1376 | xx = m2[cube.r1][cube.g1][cube.b1] 1377 | -m2[cube.r1][cube.g1][cube.b0] 1378 | -m2[cube.r1][cube.g0][cube.b1] 1379 | +m2[cube.r1][cube.g0][cube.b0] 1380 | -m2[cube.r0][cube.g1][cube.b1] 1381 | +m2[cube.r0][cube.g1][cube.b0] 1382 | +m2[cube.r0][cube.g0][cube.b1] 1383 | -m2[cube.r0][cube.g0][cube.b0]; 1384 | return xx - (dr*dr + dg*dg + db*db)/Vol(cube,wt); 1385 | } 1386 | 1387 | /* We want to minimize the sum of the variances of two subboxes. 1388 | * The sum(c^2) terms can be ignored since their sum over both subboxes 1389 | * is the same (the sum for the whole box) no matter where we split. 1390 | * The remaining terms have a minus sign in the variance formula, 1391 | * so we drop the minus sign and MAXIMIZE the sum of the two terms. 1392 | */ 1393 | private float Maximize(Box cube, int dir, int first, int last, int cut[], 1394 | long whole_r, long whole_g, long whole_b, long whole_w) { 1395 | long half_r, half_g, half_b, half_w; 1396 | long base_r, base_g, base_b, base_w; 1397 | int i; 1398 | float temp, max; 1399 | 1400 | base_r = Bottom(cube, dir, mr); 1401 | base_g = Bottom(cube, dir, mg); 1402 | base_b = Bottom(cube, dir, mb); 1403 | base_w = Bottom(cube, dir, wt); 1404 | 1405 | max = 0.0f; 1406 | cut[0] = -1; 1407 | 1408 | for(i = first; i < last; ++i){ 1409 | half_r = base_r + Top(cube, dir, i, mr); 1410 | half_g = base_g + Top(cube, dir, i, mg); 1411 | half_b = base_b + Top(cube, dir, i, mb); 1412 | half_w = base_w + Top(cube, dir, i, wt); 1413 | /* now half_x is sum over lower half of box, if split at i */ 1414 | if (half_w == 0) /* subbox could be empty of pixels! */ 1415 | continue; /* never split into an empty box */ 1416 | temp = (half_r*half_r + half_g*half_g + half_b*half_b)/(float)half_w; 1417 | half_r = whole_r - half_r; 1418 | half_g = whole_g - half_g; 1419 | half_b = whole_b - half_b; 1420 | half_w = whole_w - half_w; 1421 | if (half_w == 0) /* subbox could be empty of pixels! */ 1422 | continue; /* never split into an empty box */ 1423 | temp += (half_r*half_r + half_g*half_g + half_b*half_b)/(float)half_w; 1424 | 1425 | if (temp > max) { max = temp; cut[0] = i;} 1426 | } 1427 | 1428 | return max; 1429 | } 1430 | 1431 | private boolean Cut(Box set1, Box set2) { 1432 | int dir; 1433 | int cutr[] = new int[1]; 1434 | int cutg[] = new int[1]; 1435 | int cutb[] = new int[1]; 1436 | float maxr, maxg, maxb; 1437 | long whole_r, whole_g, whole_b, whole_w; 1438 | 1439 | whole_r = Vol(set1, mr); 1440 | whole_g = Vol(set1, mg); 1441 | whole_b = Vol(set1, mb); 1442 | whole_w = Vol(set1, wt); 1443 | 1444 | maxr = Maximize(set1, RED, set1.r0 + 1, set1.r1, cutr, 1445 | whole_r, whole_g, whole_b, whole_w); 1446 | maxg = Maximize(set1, GREEN, set1.g0 + 1, set1.g1, cutg, 1447 | whole_r, whole_g, whole_b, whole_w); 1448 | maxb = Maximize(set1, BLUE, set1.b0 + 1, set1.b1, cutb, 1449 | whole_r, whole_g, whole_b, whole_w); 1450 | 1451 | if(maxr >= maxg && maxr >= maxb) { 1452 | dir = RED; 1453 | if (cutr[0] < 0) return false; /* can't split the box */ 1454 | } else if(maxg >= maxr && maxg >= maxb) 1455 | dir = GREEN; 1456 | else 1457 | dir = BLUE; 1458 | 1459 | set2.r1 = set1.r1; 1460 | set2.g1 = set1.g1; 1461 | set2.b1 = set1.b1; 1462 | 1463 | switch (dir){ 1464 | case RED: 1465 | set2.r0 = set1.r1 = cutr[0]; 1466 | set2.g0 = set1.g0; 1467 | set2.b0 = set1.b0; 1468 | break; 1469 | case GREEN: 1470 | set2.g0 = set1.g1 = cutg[0]; 1471 | set2.r0 = set1.r0; 1472 | set2.b0 = set1.b0; 1473 | break; 1474 | case BLUE: 1475 | set2.b0 = set1.b1 = cutb[0]; 1476 | set2.r0 = set1.r0; 1477 | set2.g0 = set1.g0; 1478 | break; 1479 | } 1480 | set1.vol = (set1.r1 - set1.r0)*(set1.g1 - set1.g0)*(set1.b1 - set1.b0); 1481 | set2.vol = (set2.r1 - set2.r0)*(set2.g1 - set2.g0)*(set2.b1 - set2.b0); 1482 | 1483 | return true; 1484 | } 1485 | 1486 | private void Mark(Box cube, int label, int tag[]) { 1487 | int r, g, b; 1488 | 1489 | for(r = cube.r0 + 1; r <= cube.r1; ++r) 1490 | for(g = cube.g0 + 1; g <= cube.g1; ++g) 1491 | for(b = cube.b0 + 1; b <= cube.b1; ++b) 1492 | tag[(r<<10) + (r<<6) + r + (g<<5) + g + b] = label; 1493 | } 1494 | } 1495 | 1496 | /** 1497 | * A hash table using primitive integer keys. 1498 | * 1499 | * Based on 1500 | * QuadraticProbingHashTable.java 1501 | *

1502 | * Probing table implementation of hash tables. 1503 | * Note that all "matching" is based on the equals method. 1504 | * 1505 | * @author Mark Allen Weiss 1506 | */ 1507 | private static class IntHashtable { 1508 | 1509 | /** The array of HashEntry. */ 1510 | private HashEntry [ ] array; // The array of HashEntry 1511 | private int currentSize; // The number of occupied cells 1512 | 1513 | /** 1514 | * Construct the hash table. 1515 | * @param size the approximate initial size. 1516 | */ 1517 | @SuppressWarnings("unchecked") 1518 | public IntHashtable(int size) { 1519 | array = new HashEntry [size]; 1520 | makeEmpty( ); 1521 | } 1522 | 1523 | /** 1524 | * Insert into the hash table. If the item is 1525 | * already present, do nothing. 1526 | * @param key the item to insert. 1527 | */ 1528 | public void put(int key, E value) { 1529 | // Insert key as active 1530 | int currentPos = locate( key ); 1531 | if( isActive( currentPos ) ) 1532 | return; 1533 | 1534 | array[ currentPos ] = new HashEntry ( key, value, true ); 1535 | 1536 | // Rehash 1537 | if( ++currentSize > array.length / 2 ) 1538 | rehash( ); 1539 | } 1540 | 1541 | /** 1542 | * Expand the hash table. 1543 | */ 1544 | @SuppressWarnings("unchecked") 1545 | private void rehash( ) { 1546 | HashEntry [ ] oldArray = array; 1547 | 1548 | // Create a new double-sized, empty table 1549 | array = new HashEntry [nextPrime( 2 * oldArray.length )]; 1550 | currentSize = 0; 1551 | 1552 | // Copy table over 1553 | for( int i = 0; i < oldArray.length; i++ ) 1554 | if( oldArray[i] != null && oldArray[i].isActive ) 1555 | put( oldArray[i].key, oldArray[i].value ); 1556 | 1557 | return; 1558 | } 1559 | 1560 | /** 1561 | * Method that performs quadratic probing resolution. 1562 | * @param key the item to search for. 1563 | * @return the index of the item. 1564 | */ 1565 | private int locate(int key) { 1566 | int collisionNum = 0; 1567 | 1568 | // And with the largest positive integer 1569 | int currentPos = (key & 0x7FFFFFFF) % array.length; 1570 | 1571 | while( array[ currentPos ] != null && 1572 | array[ currentPos ].key != key ) 1573 | { 1574 | currentPos += 2 * ++collisionNum - 1; // Compute ith probe 1575 | if( currentPos >= array.length ) // Implement the mod 1576 | currentPos -= array.length; 1577 | } 1578 | return currentPos; 1579 | } 1580 | 1581 | /** 1582 | * Find an item in the hash table. 1583 | * @param key the item to search for. 1584 | * @return the value of the matching item. 1585 | */ 1586 | public E get(int key) { 1587 | int currentPos = locate( key ); 1588 | return isActive( currentPos ) ? array[ currentPos ].value : null; 1589 | } 1590 | 1591 | /** 1592 | * Return true if currentPos exists and is active. 1593 | * @param currentPos the result of a call to findPos. 1594 | * @return true if currentPos is active. 1595 | */ 1596 | private boolean isActive( int currentPos ) { 1597 | return array[ currentPos ] != null && array[ currentPos ].isActive; 1598 | } 1599 | 1600 | /** 1601 | * Make the hash table logically empty. 1602 | */ 1603 | public void makeEmpty( ) { 1604 | currentSize = 0; 1605 | for( int i = 0; i < array.length; i++ ) 1606 | array[ i ] = null; 1607 | } 1608 | 1609 | /** 1610 | * Internal method to find a prime number at least as large as n. 1611 | * @param n the starting number (must be positive). 1612 | * @return a prime number larger than or equal to n. 1613 | */ 1614 | private static int nextPrime(int n) { 1615 | if( n % 2 == 0 ) 1616 | n++; 1617 | 1618 | for( ; !isPrime( n ); n += 2 ) 1619 | ; 1620 | 1621 | return n; 1622 | } 1623 | 1624 | /** 1625 | * Internal method to test if a number is prime. 1626 | * Not an efficient algorithm. 1627 | * @param n the number to test. 1628 | * @return the result of the test. 1629 | */ 1630 | private static boolean isPrime(int n) { 1631 | if( n == 2 || n == 3 ) 1632 | return true; 1633 | 1634 | if( n == 1 || n % 2 == 0 ) 1635 | return false; 1636 | 1637 | for( int i = 3; i * i <= n; i += 2 ) 1638 | if( n % i == 0 ) 1639 | return false; 1640 | 1641 | return true; 1642 | } 1643 | 1644 | // The basic entry stored in ProbingHashTable 1645 | private static class HashEntry { 1646 | int key; // the key 1647 | V value; // the value 1648 | boolean isActive; // false if deleted 1649 | 1650 | @SuppressWarnings("unused") 1651 | HashEntry(int k, V val) { 1652 | this( k, val, true ); 1653 | } 1654 | 1655 | HashEntry(int k, V val, boolean i) { 1656 | key = k; 1657 | value = val; 1658 | isActive = i; 1659 | } 1660 | } 1661 | } 1662 | 1663 | private static class InverseColorMap { 1664 | private int bitsReserved;// Number of bits used in color quantization. 1665 | private int bitsDiscarded;// Number of discarded bits 1666 | private int maxColorVal;// Maximum value for each quantized color 1667 | private int invMapLen;// Length of the inverse color map 1668 | // The inverse color map itself 1669 | private byte[] invColorMap; 1670 | 1671 | // Default constructor using 5 for quantization bits 1672 | public InverseColorMap() { 1673 | this(5); 1674 | } 1675 | 1676 | // Constructor using bitsReserved bits for quantization 1677 | public InverseColorMap(int rbits) { 1678 | bitsReserved = rbits; 1679 | bitsDiscarded = 8 - bitsReserved; 1680 | maxColorVal = 1 << bitsReserved; 1681 | invMapLen = maxColorVal * maxColorVal * maxColorVal; 1682 | invColorMap = new byte[invMapLen]; 1683 | } 1684 | 1685 | // Fetch the forward color map index for this RGB 1686 | public int getNearestColorIndex(int red, int green, int blue) { 1687 | return invColorMap[(((red >> bitsDiscarded) << (bitsReserved<<1))) | 1688 | ((green >> bitsDiscarded) << bitsReserved) | 1689 | (blue >> bitsDiscarded)]&0xff; 1690 | } 1691 | 1692 | /** 1693 | * Create an inverse color map using the input forward RGB map. 1694 | */ 1695 | public void createInverseMap(int no_of_colors, int[] colorPalette) { 1696 | int red, green, blue, r, g, b; 1697 | int rdist, gdist, bdist, dist; 1698 | int rinc, ginc, binc; 1699 | 1700 | int x = (1 << bitsDiscarded);// Step size for each color 1701 | int xsqr = (1 << (bitsDiscarded + bitsDiscarded)); 1702 | int txsqr = xsqr + xsqr; 1703 | int buf_index; 1704 | 1705 | int[] dist_buf = new int[invMapLen]; 1706 | 1707 | // Initialize the distance buffer array with the largest integer value 1708 | for (int i = invMapLen; --i >= 0;) 1709 | dist_buf[i] = 0x7FFFFFFF; 1710 | // Now loop through all the colors in the color map 1711 | for (int i = 0; i < no_of_colors; i++) 1712 | { 1713 | red = ((colorPalette[i]>>16)&0xff); 1714 | green = ((colorPalette[i]>>8)&0xff); 1715 | blue = (colorPalette[i]&0xff); 1716 | /** 1717 | * We start from the origin (0,0,0) of the quantized colors, calculate 1718 | * the distance between the cell center of the quantized colors and 1719 | * the current color map entry as follows: 1720 | * (rcenter * x + x/2) - red, where rcenter is the center of the 1721 | * Quantized red color map entry which is 0 since we start from 0. 1722 | */ 1723 | rdist = (x>>1) - red;// Red distance 1724 | gdist = (x>>1) - green;// Green distance 1725 | bdist = (x>>1) - blue;// Blue distance 1726 | dist = rdist*rdist + gdist*gdist + bdist*bdist;//The modular 1727 | // The distance increment with each step value x 1728 | rinc = txsqr - (red << (bitsDiscarded + 1)); 1729 | ginc = txsqr - (green << (bitsDiscarded + 1)); 1730 | binc = txsqr - (blue << (bitsDiscarded + 1)); 1731 | 1732 | buf_index = 0; 1733 | // Loop through quantized RGB space 1734 | for (r = 0, rdist = dist; r < maxColorVal; rdist += rinc, rinc += txsqr, r++ ) 1735 | { 1736 | for (g = 0, gdist = rdist; g < maxColorVal; gdist += ginc, ginc += txsqr, g++) 1737 | { 1738 | for (b = 0, bdist = gdist; b < maxColorVal; bdist += binc, binc += txsqr, buf_index++, b++) 1739 | { 1740 | if (bdist < dist_buf[buf_index]) 1741 | { 1742 | dist_buf[buf_index] = bdist; 1743 | invColorMap[buf_index] = (byte)i; 1744 | } 1745 | } 1746 | } 1747 | } 1748 | } 1749 | } 1750 | } 1751 | } -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/App.java: -------------------------------------------------------------------------------- 1 | package mayaseii.wildsmoddingtool; 2 | 3 | public class App 4 | { 5 | public static void main(String[] args) 6 | { 7 | Application.main(args); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/Application.java: -------------------------------------------------------------------------------- 1 | package mayaseii.wildsmoddingtool; 2 | 3 | import javafx.fxml.FXMLLoader; 4 | import javafx.scene.Scene; 5 | import javafx.scene.image.Image; 6 | import javafx.scene.layout.AnchorPane; 7 | import javafx.scene.media.MediaPlayer; 8 | import javafx.scene.media.MediaView; 9 | import javafx.scene.text.Font; 10 | import javafx.stage.Stage; 11 | import javafx.scene.media.Media; 12 | import javafx.util.Duration; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.io.IOException; 16 | import java.net.URISyntaxException; 17 | import java.util.Locale; 18 | import java.util.Objects; 19 | import java.util.ResourceBundle; 20 | 21 | public class Application extends javafx.application.Application 22 | { 23 | static MediaPlayer mediaPlayer = null; 24 | 25 | public static Locale locale; 26 | 27 | @Override 28 | public void start(@NotNull Stage stage) throws IOException 29 | { 30 | // Resolves the locale. 31 | // locale = new Locale("es"); 32 | locale = Locale.getDefault(); 33 | ResourceBundle bundle = ResourceBundle.getBundle("mayaseii.wildsmoddingtool.strings", locale); 34 | 35 | // Loads the custom Pokémon font. 36 | Font.loadFont(Objects.requireNonNull(getClass().getResource("PokeWilds-Regular.ttf")).toExternalForm().replace("%20", " "), 16); 37 | 38 | // Loads the scene from the FXML file. 39 | FXMLLoader fxmlLoader = new FXMLLoader(Application.class.getResource("main-menu.fxml"), bundle); 40 | Scene scene = new Scene(fxmlLoader.load()); 41 | 42 | // Loads the CSS file to apply to the view. 43 | String css = Objects.requireNonNull(this.getClass().getResource("app.css")).toExternalForm(); 44 | scene.getStylesheets().add(css); 45 | 46 | // Sets up the media player for the background music. 47 | this.setupMediaPlayer(); 48 | 49 | // Prepares the app window. 50 | stage.getIcons().add(new Image("file:src/icon.png")); 51 | stage.setTitle(bundle.getString("Global.AppTitle")); 52 | stage.setResizable(false); 53 | 54 | // Shows the stage. 55 | stage.setScene(scene); 56 | stage.show(); 57 | 58 | // Starts the media player. 59 | this.startMediaPlayer(scene); 60 | } 61 | 62 | public static void main(String[] args) 63 | { 64 | launch(args); 65 | } 66 | 67 | private void setupMediaPlayer() 68 | { 69 | Media media = null; 70 | 71 | // Loads the background music track. 72 | try { media = new Media(Objects.requireNonNull(getClass().getResource("audio/BG_VioletCity.wav")).toURI().toString()); } 73 | catch (URISyntaxException e) { e.printStackTrace(); } 74 | 75 | assert media != null; 76 | mediaPlayer = new MediaPlayer(media); 77 | mediaPlayer.setVolume(0.3); 78 | 79 | // Enables looping. 80 | mediaPlayer.setOnEndOfMedia(() -> 81 | { 82 | mediaPlayer.seek(Duration.seconds(2.2)); 83 | mediaPlayer.play(); 84 | }); 85 | 86 | mediaPlayer.setCycleCount(Integer.MAX_VALUE); 87 | } 88 | 89 | private void startMediaPlayer(@NotNull Scene scene) 90 | { 91 | // Plays the background track. 92 | mediaPlayer.setAutoPlay(true); 93 | 94 | // Adds the media player to the scene. 95 | MediaView mediaView = new MediaView(mediaPlayer); 96 | ((AnchorPane)scene.getRoot()).getChildren().add(mediaView); 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/ColorMapper.java: -------------------------------------------------------------------------------- 1 | package mayaseii.wildsmoddingtool; 2 | 3 | import java.awt.*; 4 | import java.awt.image.LookupTable; 5 | import java.util.Arrays; 6 | 7 | public class ColorMapper extends LookupTable { 8 | 9 | private final int[] from; 10 | private final int[] to; 11 | 12 | public ColorMapper(Color from, 13 | Color to) { 14 | super(0, 4); 15 | 16 | this.from = new int[] { 17 | from.getRed(), 18 | from.getGreen(), 19 | from.getBlue(), 20 | from.getAlpha(), 21 | }; 22 | this.to = new int[] { 23 | to.getRed(), 24 | to.getGreen(), 25 | to.getBlue(), 26 | to.getAlpha(), 27 | }; 28 | } 29 | 30 | @Override 31 | public int[] lookupPixel(int[] src, 32 | int[] dest) { 33 | if (dest == null) { 34 | dest = new int[src.length]; 35 | } 36 | 37 | int[] newColor = (Arrays.equals(src, from) ? to : src); 38 | System.arraycopy(newColor, 0, dest, 0, newColor.length); 39 | 40 | return dest; 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/Controller.java: -------------------------------------------------------------------------------- 1 | package mayaseii.wildsmoddingtool; 2 | 3 | import javafx.event.ActionEvent; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.Node; 6 | import javafx.scene.Parent; 7 | import javafx.scene.Scene; 8 | import javafx.scene.image.Image; 9 | import javafx.scene.image.ImageView; 10 | import javafx.scene.input.MouseEvent; 11 | import javafx.stage.Stage; 12 | 13 | import java.io.IOException; 14 | import java.net.URI; 15 | import java.net.URISyntaxException; 16 | import java.util.Locale; 17 | import java.util.Objects; 18 | 19 | import java.awt.*; 20 | import java.util.ResourceBundle; 21 | 22 | public class Controller 23 | { 24 | public void volumeControl(MouseEvent e) throws URISyntaxException 25 | { 26 | // Gets the volume control image view. 27 | ImageView image = (ImageView)e.getSource(); 28 | 29 | // Checks whether the music is on. 30 | if (Application.mediaPlayer.getVolume() == .2) 31 | { 32 | // Disables the music. 33 | Application.mediaPlayer.setVolume(0); 34 | image.setImage(new Image(Objects.requireNonNull(this.getClass().getResource("img/noVolume.png")).toURI().toString())); 35 | } 36 | else 37 | { 38 | // Enables the music. 39 | Application.mediaPlayer.setVolume(.2); 40 | image.setImage(new Image(Objects.requireNonNull(this.getClass().getResource("img/volume.png")).toURI().toString())); 41 | } 42 | } 43 | 44 | public void openSeiiLink(ActionEvent e) throws URISyntaxException, IOException 45 | { 46 | // Opens Seiiccubus' Twitter link on the client's browser. 47 | Desktop.getDesktop().browse(new URI("https://twitter.com/mayaseii")); 48 | } 49 | 50 | public void playerSkinScene(ActionEvent e) throws IOException 51 | { 52 | switchScene((Stage)((Node)e.getSource()).getScene().getWindow(), "player-skin.fxml", "player-skin.css"); 53 | } 54 | 55 | private void switchScene(Stage stage, String sceneName, String cssFile) throws IOException 56 | { 57 | // Resolves the locale. 58 | ResourceBundle bundle = ResourceBundle.getBundle("mayaseii.wildsmoddingtool.strings", Application.locale); 59 | 60 | // Loads the scene. 61 | Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource(sceneName)), bundle); 62 | Scene scene = new Scene(root); 63 | 64 | // Loads the CSS file to apply to the view. 65 | String css = Objects.requireNonNull(this.getClass().getResource(cssFile)).toExternalForm(); 66 | scene.getStylesheets().add(css); 67 | 68 | // Shows the stage. 69 | stage.setScene(scene); 70 | stage.centerOnScreen(); 71 | stage.show(); 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/GifSequenceWriter.java: -------------------------------------------------------------------------------- 1 | package mayaseii.wildsmoddingtool;// 2 | // GifSequenceWriter.java 3 | // 4 | // Created by Elliot Kroo on 2009-04-25. 5 | // 6 | // This work is licensed under the Creative Commons Attribution 3.0 Unported 7 | // License. To view a copy of this license, visit 8 | // http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative 9 | // Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. 10 | 11 | 12 | import javax.imageio.*; 13 | import javax.imageio.metadata.*; 14 | import javax.imageio.stream.*; 15 | import java.awt.image.*; 16 | import java.io.*; 17 | import java.util.Iterator; 18 | 19 | public class GifSequenceWriter { 20 | protected ImageWriter gifWriter; 21 | protected ImageWriteParam imageWriteParam; 22 | protected IIOMetadata imageMetaData; 23 | 24 | /** 25 | * Creates a new GifSequenceWriter 26 | * 27 | * @param outputStream the ImageOutputStream to be written to 28 | * @param imageType one of the imageTypes specified in BufferedImage 29 | * @param timeBetweenFramesMS the time between frames in miliseconds 30 | * @param loopContinuously wether the gif should loop repeatedly 31 | * @throws IIOException if no gif ImageWriters are found 32 | * 33 | * @author Elliot Kroo (elliot[at]kroo[dot]net) 34 | */ 35 | public GifSequenceWriter( 36 | ImageOutputStream outputStream, 37 | int imageType, 38 | int timeBetweenFramesMS, 39 | boolean loopContinuously) throws IIOException, IOException { 40 | // my method to create a writer 41 | gifWriter = getWriter(); 42 | imageWriteParam = gifWriter.getDefaultWriteParam(); 43 | ImageTypeSpecifier imageTypeSpecifier = 44 | ImageTypeSpecifier.createFromBufferedImageType(imageType); 45 | 46 | imageMetaData = 47 | gifWriter.getDefaultImageMetadata(imageTypeSpecifier, 48 | imageWriteParam); 49 | 50 | String metaFormatName = imageMetaData.getNativeMetadataFormatName(); 51 | 52 | IIOMetadataNode root = (IIOMetadataNode) 53 | imageMetaData.getAsTree(metaFormatName); 54 | 55 | IIOMetadataNode graphicsControlExtensionNode = getNode( 56 | root, 57 | "GraphicControlExtension"); 58 | 59 | graphicsControlExtensionNode.setAttribute("disposalMethod", "none"); 60 | graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE"); 61 | graphicsControlExtensionNode.setAttribute( 62 | "transparentColorFlag", 63 | "FALSE"); 64 | graphicsControlExtensionNode.setAttribute( 65 | "delayTime", 66 | Integer.toString(timeBetweenFramesMS / 10)); 67 | graphicsControlExtensionNode.setAttribute( 68 | "transparentColorIndex", 69 | "0"); 70 | 71 | IIOMetadataNode commentsNode = getNode(root, "CommentExtensions"); 72 | commentsNode.setAttribute("CommentExtension", "Created by MAH"); 73 | 74 | IIOMetadataNode appEntensionsNode = getNode( 75 | root, 76 | "ApplicationExtensions"); 77 | 78 | IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension"); 79 | 80 | child.setAttribute("applicationID", "NETSCAPE"); 81 | child.setAttribute("authenticationCode", "2.0"); 82 | 83 | int loop = loopContinuously ? 0 : 1; 84 | 85 | child.setUserObject(new byte[]{ 0x1, (byte) (loop & 0xFF), (byte) 86 | ((loop >> 8) & 0xFF)}); 87 | appEntensionsNode.appendChild(child); 88 | 89 | imageMetaData.setFromTree(metaFormatName, root); 90 | 91 | gifWriter.setOutput(outputStream); 92 | 93 | gifWriter.prepareWriteSequence(null); 94 | } 95 | 96 | public void writeToSequence(RenderedImage img) throws IOException { 97 | gifWriter.writeToSequence( 98 | new IIOImage( 99 | img, 100 | null, 101 | imageMetaData), 102 | imageWriteParam); 103 | } 104 | 105 | /** 106 | * Close this GifSequenceWriter object. This does not close the underlying 107 | * stream, just finishes off the GIF. 108 | */ 109 | public void close() throws IOException { 110 | gifWriter.endWriteSequence(); 111 | } 112 | 113 | /** 114 | * Returns the first available GIF ImageWriter using 115 | * ImageIO.getImageWritersBySuffix("gif"). 116 | * 117 | * @return a GIF ImageWriter object 118 | * @throws IIOException if no GIF image writers are returned 119 | */ 120 | private static ImageWriter getWriter() throws IIOException { 121 | Iterator iter = ImageIO.getImageWritersBySuffix("gif"); 122 | if(!iter.hasNext()) { 123 | throw new IIOException("No GIF Image Writers Exist"); 124 | } else { 125 | return iter.next(); 126 | } 127 | } 128 | 129 | /** 130 | * Returns an existing child node, or creates and returns a new child node (if 131 | * the requested node does not exist). 132 | * 133 | * @param rootNode the IIOMetadataNode to search for the child node. 134 | * @param nodeName the name of the child node. 135 | * 136 | * @return the child node, if found or a new node created with the given name. 137 | */ 138 | private static IIOMetadataNode getNode( 139 | IIOMetadataNode rootNode, 140 | String nodeName) { 141 | int nNodes = rootNode.getLength(); 142 | for (int i = 0; i < nNodes; i++) { 143 | if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) 144 | == 0) { 145 | return((IIOMetadataNode) rootNode.item(i)); 146 | } 147 | } 148 | IIOMetadataNode node = new IIOMetadataNode(nodeName); 149 | rootNode.appendChild(node); 150 | return(node); 151 | } 152 | 153 | /** 154 | public GifSequenceWriter( 155 | BufferedOutputStream outputStream, 156 | int imageType, 157 | int timeBetweenFramesMS, 158 | boolean loopContinuously) { 159 | 160 | */ 161 | 162 | public static void main(String[] args) throws Exception { 163 | if (args.length > 1) { 164 | // grab the output image type from the first image in the sequence 165 | BufferedImage firstImage = ImageIO.read(new File(args[0])); 166 | 167 | // create a new BufferedOutputStream with the last argument 168 | ImageOutputStream output = 169 | new FileImageOutputStream(new File(args[args.length - 1])); 170 | 171 | // create a gif sequence with the type of the first image, 1 second 172 | // between frames, which loops continuously 173 | GifSequenceWriter writer = 174 | new GifSequenceWriter(output, firstImage.getType(), 1, false); 175 | 176 | // write out the first image to our sequence... 177 | writer.writeToSequence(firstImage); 178 | for(int i=1; i _fromColours = new ArrayList<>(); 66 | 67 | private static boolean _neverOpened = true; 68 | 69 | public void initialize(URL location, @NotNull ResourceBundle bundle) 70 | { 71 | resizeUsingNearestNeighbour(leftPane); 72 | resizeUsingNearestNeighbour(rightPane); 73 | 74 | lockPaneDivider(); 75 | limitNameFieldLength(); 76 | 77 | // Sets the default character and author names. 78 | nameLabel.setText(nameField.getText()); 79 | authorField.setText(bundle.getString("PlayerSkin.Author")); 80 | 81 | initialiseErrorPane(); 82 | hidePreviewPane(); 83 | 84 | _bundle = bundle; 85 | } 86 | 87 | private void initialiseErrorPane() 88 | { 89 | errorLabel.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS); 90 | errorPane.setVisible(false); 91 | } 92 | 93 | private void limitNameFieldLength() 94 | { 95 | Pattern pattern = Pattern.compile(".{0,16}"); 96 | TextFormatter formatter = new TextFormatter<>(change -> pattern.matcher(change.getControlNewText()).matches() ? change : null); 97 | nameField.setTextFormatter(formatter); 98 | } 99 | 100 | private void lockPaneDivider() 101 | { 102 | SplitPane.Divider divider = splitPane.getDividers().get(0); 103 | double position = divider.getPosition(); 104 | divider.positionProperty().addListener((observable, oldValue, newValue) -> divider.setPosition(position)); 105 | } 106 | 107 | private void resizeUsingNearestNeighbour(@NotNull AnchorPane pane) 108 | { 109 | for (Node child : pane.getChildren()) 110 | { 111 | if (child instanceof ImageView view) 112 | { 113 | Image newImage = new Image(view.getImage().getUrl(), view.getFitWidth(), view.getFitHeight(), true, false); 114 | view.setImage(newImage); 115 | } 116 | } 117 | } 118 | 119 | public void backToMenu(@NotNull ActionEvent e) throws IOException 120 | { 121 | Stage stage = (Stage)((Node)e.getSource()).getScene().getWindow(); 122 | 123 | // Resolves the locale. 124 | ResourceBundle bundle = ResourceBundle.getBundle("mayaseii.wildsmoddingtool.strings", Application.locale); 125 | 126 | // Loads the scene. 127 | Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("main-menu.fxml")), bundle); 128 | Scene scene = new Scene(root); 129 | 130 | // Loads the CSS file to apply to the view. 131 | String css = Objects.requireNonNull(this.getClass().getResource("app.css")).toExternalForm(); 132 | scene.getStylesheets().add(css); 133 | 134 | // Shows the stage. 135 | stage.setScene(scene); 136 | stage.show(); 137 | } 138 | 139 | public void characterNameChanged(@NotNull InputEvent e) 140 | { 141 | // Updates the name label to reflect the new text. 142 | TextField textField = (TextField) e.getSource(); 143 | nameLabel.setText(textField.getText()); 144 | } 145 | 146 | public void uploadImage(@NotNull MouseEvent e) 147 | { 148 | ImageView imageView = (ImageView) e.getSource(); 149 | 150 | File file = getFile(); 151 | if (file == null) return; // Tests if image file is null. 152 | 153 | // Stores the chosen image. 154 | Image image = new Image(file.toURI().toString()); 155 | boolean isSheet = isSpriteSheet(imageView, image); 156 | @NonNls String spriteType = imageView.getId().split("-")[0]; // E.g., walking, running. 157 | 158 | if (!isSheet && !imageFitsView(imageView, image)) displayDimensionsErrorPopup(imageView, spriteType); 159 | else showUploadedImage(imageView, file, image, isSheet, spriteType); 160 | } 161 | 162 | private void showUploadedImage(ImageView imageView, File file, Image image, boolean isSheet, String spriteType) 163 | { 164 | if (!isSheet) displayIndividualSprite(imageView, file); 165 | else displaySpriteSheet(file, image, spriteType); 166 | } 167 | 168 | private void displayDimensionsErrorPopup(ImageView imageView, String spriteType) 169 | { 170 | Vector2 dimensions = getSpriteSheetDimensions(spriteType); 171 | String popupText = getDimensionsErrorText(imageView, (int) dimensions.x, (int) dimensions.y); 172 | 173 | displayInfoPopup(popupText); 174 | } 175 | 176 | private void displaySpriteSheet(@NotNull File file, Image image, @NonNls String spriteType) 177 | { 178 | // Resizes the image. 179 | image = new Image(file.toURI().toString(), image.getWidth() * 2, image.getHeight() * 2, true, false); 180 | 181 | // Prepares the counter. 182 | int x = 0; 183 | 184 | // Loops through all image view nodes. 185 | for (Node child : rightPane.getChildren()) 186 | { 187 | // Checks if the image view has a relevant ID. 188 | if (child instanceof ImageView view && view.getId() != null && view.getId().contains(spriteType)) 189 | { 190 | // Crops the image accordingly. 191 | WritableImage newImage = cropImage(image, x, view); 192 | 193 | // Sets the image for each view. 194 | view.setImage(newImage); 195 | view.setOpacity(1); 196 | 197 | // Displays the image on the corresponding file-name image view. 198 | ImageView fileView = (ImageView) view.getScene().lookup('#' + view.getId() + "-1"); 199 | fileView.setImage(newImage); 200 | fileView.setOpacity(1); 201 | 202 | // Increases the X coordinate for the next sprite. 203 | x += view.getFitWidth(); 204 | } 205 | } 206 | } 207 | 208 | private @NotNull WritableImage cropImage(@NotNull Image image, int x, @NotNull ImageView view) 209 | { 210 | PixelReader reader = image.getPixelReader(); 211 | return new WritableImage(reader, x, (int) (image.getHeight() - view.getFitHeight()), (int) view.getFitWidth(), (int) view.getFitHeight()); 212 | } 213 | 214 | private void displayIndividualSprite(@NotNull ImageView imageView, @NotNull File file) 215 | { 216 | // Resizes the image. 217 | Image image = new Image(file.toURI().toString(), imageView.getFitWidth(), imageView.getFitHeight(), true, false); 218 | 219 | // Displays the image on the image view. 220 | imageView.setImage(image); 221 | imageView.setOpacity(1); 222 | 223 | // Displays the image on the corresponding file-name image view. 224 | ImageView fileView = (ImageView) imageView.getScene().lookup('#' + imageView.getId() + "-1"); 225 | if (fileView == null) return; 226 | 227 | fileView.setImage(image); 228 | fileView.setOpacity(1); 229 | } 230 | 231 | private boolean imageFitsView(@NotNull ImageView imageView, @NotNull Image image) 232 | { 233 | return image.getWidth() == imageView.getFitWidth() / 2 && image.getHeight() == imageView.getFitHeight() / 2; 234 | } 235 | 236 | private Vector2 getSpriteSheetDimensions(@NonNls String spriteType) 237 | { 238 | Vector2 dimensions = Vector2.Zero; 239 | 240 | // Gets the sprite sheet dimensions. 241 | for (Node child : rightPane.getChildren()) 242 | { 243 | if (child instanceof ImageView view && view.getId() != null && view.getId().contains(spriteType)) 244 | { 245 | dimensions.x += view.getFitWidth() / 2; 246 | if (dimensions.y == 0) dimensions.y = view.getFitHeight() / 2; 247 | } 248 | } 249 | return dimensions; 250 | } 251 | 252 | private @NonNls @NotNull String getDimensionsErrorText(@NotNull ImageView imageView, int width, int height) 253 | { 254 | @NonNls String popupText = _bundle.getString("PlayerSkin.IncorrectDimensions") + " "; 255 | popupText += (int)(imageView.getFitWidth() / 2) + "x" + (int)(imageView.getFitHeight() / 2) + "px"; 256 | popupText += " (" + _bundle.getString("PlayerSkin.Sprite") + ") "; 257 | popupText += _bundle.getString("PlayerSkin.Or") + " " + width + "x" + height + "px (" + _bundle.getString("PlayerSkin.SpriteSheet") + ")."; 258 | return popupText; 259 | } 260 | 261 | private void displayInfoPopup(@NonNls String popupText) 262 | { 263 | errorLabel.setText(popupText); 264 | errorPane.setVisible(true); 265 | } 266 | 267 | private boolean isSpriteSheet(@NotNull ImageView imageView, Image image) 268 | { 269 | boolean isSheet = imageView.getId().contains("walking") && image.getWidth() == 128 && image.getHeight() == 16; 270 | isSheet |= imageView.getId().contains("running") && image.getWidth() == 128 && image.getHeight() == 16; 271 | isSheet |= imageView.getId().contains("sitting") && image.getWidth() == 48 && image.getHeight() == 16; 272 | isSheet |= imageView.getId().contains("fishing") && image.getWidth() == 56 && image.getHeight() == 24; 273 | return isSheet; 274 | } 275 | 276 | private File getFile() 277 | { 278 | FileChooser fileChooser = new FileChooser(); 279 | FileChooser.ExtensionFilter extFilterPNG = new FileChooser.ExtensionFilter("PNG files (*.PNG)", "*.PNG"); 280 | FileChooser.ExtensionFilter extFilterpng = new FileChooser.ExtensionFilter("png files (*.png)", "*.png"); 281 | fileChooser.getExtensionFilters().addAll(extFilterPNG, extFilterpng); 282 | 283 | // Opens the file chooser. 284 | return fileChooser.showOpenDialog(null); 285 | } 286 | 287 | public void closePopup() 288 | { 289 | // Hides the error popup. 290 | errorPane.setVisible(false); 291 | } 292 | 293 | public void downloadTemplate() throws IOException 294 | { 295 | String selectedDirPath = getUserChosenDirectory("PlayerSkin.SaveTemplate"); 296 | if (selectedDirPath == null) return; 297 | 298 | Path to = Paths.get(selectedDirPath + "/Wilds_PlayerSkinTemplate.zip"); 299 | InputStream from = getClass().getResourceAsStream("misc/Wilds_PlayerSkinTemplate.zip"); 300 | 301 | Files.copy(Objects.requireNonNull(from), to, StandardCopyOption.REPLACE_EXISTING); 302 | 303 | displayTemplateSuccessPopup(selectedDirPath); 304 | } 305 | 306 | private @NonNls void displayTemplateSuccessPopup(@NotNull String selectedDirPath) 307 | { 308 | @NonNls String popupText = _bundle.getString("PlayerSkin.TemplateDownloadedTo") + "\n"; 309 | popupText += selectedDirPath.replace('\\', '/'); 310 | 311 | displayInfoPopup(popupText); 312 | } 313 | 314 | private @Nullable String getUserChosenDirectory(String key) 315 | { 316 | // Creates a directory choose for the download folder. 317 | DirectoryChooser dirChooser = new DirectoryChooser(); 318 | dirChooser.setTitle(_bundle.getString(key)); 319 | 320 | // Gets the absolute path for the directory chosen. 321 | File selectedDir = dirChooser.showDialog(null); 322 | return selectedDir == null ? null : selectedDir.getAbsolutePath(); 323 | } 324 | 325 | public void downloadMod() throws IOException 326 | { 327 | String charName = nameField.getText().trim(); 328 | String authorName = authorField.getText().trim(); 329 | 330 | if (charName.isBlank()) 331 | { 332 | displayInfoPopup(String.format(_bundle.getString("PlayerSkin.MustNotBeEmpty"), _bundle.getString("PlayerSkin.CharacterName").toLowerCase())); 333 | return; 334 | } 335 | else if (authorName.isBlank()) 336 | { 337 | displayInfoPopup(String.format(_bundle.getString("PlayerSkin.MustNotBeEmpty"), _bundle.getString("PlayerSkin.ModAuthor").toLowerCase())); 338 | return; 339 | } 340 | 341 | BufferedImage[] wSprites = new BufferedImage[8]; 342 | BufferedImage[] rSprites = new BufferedImage[8]; 343 | BufferedImage[] sSprites = new BufferedImage[3]; 344 | BufferedImage[] fSprites = new BufferedImage[3]; 345 | 346 | loadSpritesIntoArrays(wSprites, rSprites, sSprites, fSprites); 347 | 348 | // Concatenates all sprite arrays. 349 | BufferedImage walkingSprite = concatenateImages(wSprites); 350 | BufferedImage runningSprite = concatenateImages(rSprites); 351 | BufferedImage sittingSprite = concatenateImages(sSprites); 352 | BufferedImage fishingSprite = concatenateImages(fSprites); 353 | 354 | // Gets individual sprites. 355 | BufferedImage sleepingSprite = getSingularSprite(sleepingView); 356 | BufferedImage frontSprite = getSingularSprite(frontView); 357 | BufferedImage backSprite = getSingularSprite(backView); 358 | 359 | // Creates a directory choose for the download folder. 360 | String selectedDirPath = getUserChosenDirectory("PlayerSkin.SaveMod"); 361 | if (selectedDirPath == null) return; 362 | 363 | // Creates the zip folder. 364 | File zip = new File(selectedDirPath + "/Wilds_PlayerSkin_" + nameField.getText() + ".zip"); 365 | ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(zip)); 366 | 367 | // Adds all sprites to the zip folder. 368 | addSpriteToZip("walking.png", walkingSprite, outputStream); 369 | addSpriteToZip("running.png", runningSprite, outputStream); 370 | addSpriteToZip("sitting.png", sittingSprite, outputStream); 371 | addSpriteToZip("fishing.png", fishingSprite, outputStream); 372 | addSpriteToZip("front.png", frontSprite, outputStream); 373 | addSpriteToZip("back.png", backSprite, outputStream); 374 | addSpriteToZip("sleepingbag.png", sleepingSprite, outputStream); 375 | 376 | addCreditsFileToZip(outputStream); 377 | addPaletteFileToZip(outputStream); 378 | 379 | outputStream.close(); 380 | 381 | displayModDownloadSuccessPopup(selectedDirPath); 382 | } 383 | 384 | private void displayModDownloadSuccessPopup(@NotNull String selectedDirPath) 385 | { 386 | // Creates the text for the success popup. 387 | @NonNls String popupText = _bundle.getString("PlayerSkin.ModCreatedIn") + "\n"; 388 | popupText += selectedDirPath.replace('\\', '/'); 389 | 390 | // Displays the success popup. 391 | displayInfoPopup(popupText); 392 | } 393 | 394 | private void addCreditsFileToZip(@NotNull ZipOutputStream outputStream) throws IOException 395 | { 396 | // Gets the bytes needed for the credits file. 397 | byte @NonNls [] data = ("Mod created by " + authorField.getText() + ".\nDo not remove this file from the mod folder.").getBytes(); 398 | 399 | // Creates and saves the credits file. 400 | ZipEntry entry = new ZipEntry("credits.txt"); 401 | outputStream.putNextEntry(entry); 402 | outputStream.write(data, 0, data.length); 403 | outputStream.closeEntry(); 404 | } 405 | 406 | private void addPaletteFileToZip(@NotNull @NonNls ZipOutputStream outputStream) throws IOException 407 | { 408 | // Ignores this file if no colours were chosen. 409 | if (_fromColours.size() == 0) return; 410 | 411 | // Gets the bytes needed for the palette file. 412 | @NonNls StringBuilder toConvert = new StringBuilder(); 413 | for (java.awt.Color colour : _fromColours) toConvert.append("\tRGB ").append(colour.getRed()).append(", ").append(colour.getGreen()).append(", ").append(colour.getBlue()).append("\n"); 414 | byte @NonNls [] data = toConvert.toString().getBytes(); 415 | 416 | // Creates and saves the palette file. 417 | ZipEntry entry = new ZipEntry("var.pal"); 418 | outputStream.putNextEntry(entry); 419 | outputStream.write(data, 0, data.length); 420 | outputStream.closeEntry(); 421 | } 422 | 423 | private void addSpriteToZip(String name, BufferedImage walkingSprite, @NotNull ZipOutputStream outputStream) throws IOException 424 | { 425 | ZipEntry entry = new ZipEntry(name); 426 | outputStream.putNextEntry(entry); 427 | ImageIO.write(walkingSprite, "png", outputStream); 428 | outputStream.closeEntry(); 429 | } 430 | 431 | private BufferedImage getSingularSprite(@NotNull ImageView view) 432 | { 433 | Image baseImage = view.getImage(); 434 | Image smallImage = scale(baseImage, (int) (baseImage.getWidth() / 2), (int) (baseImage.getHeight() / 2)); 435 | return SwingFXUtils.fromFXImage(smallImage, null); 436 | } 437 | 438 | private void loadSpritesIntoArrays(BufferedImage[] w, BufferedImage[] r, BufferedImage[] s, BufferedImage[] f) 439 | { 440 | int cWalking = 0; 441 | int cRunning = 0; 442 | int cSitting = 0; 443 | int cFishing = 0; 444 | 445 | for (Node child : rightPane.getChildren()) 446 | { 447 | // Checks if the image view has a relevant ID. 448 | if (child instanceof ImageView view && view.getId() != null) 449 | { 450 | Image baseImage = view.getImage(); 451 | Image smallImage = scale(baseImage, (int) (baseImage.getWidth() / 2), (int) (baseImage.getHeight() / 2)); 452 | 453 | if (view.getId().contains("walking")) 454 | { 455 | w[cWalking] = SwingFXUtils.fromFXImage(smallImage, null); 456 | cWalking++; 457 | } 458 | else if (view.getId().contains("running")) 459 | { 460 | r[cRunning] = SwingFXUtils.fromFXImage(smallImage, null); 461 | cRunning++; 462 | } 463 | else if (view.getId().contains("sitting")) 464 | { 465 | s[cSitting] = SwingFXUtils.fromFXImage(smallImage, null); 466 | cSitting++; 467 | } 468 | else if (view.getId().contains("fishing")) 469 | { 470 | f[cFishing] = SwingFXUtils.fromFXImage(smallImage, null); 471 | cFishing++; 472 | } 473 | } 474 | } 475 | } 476 | 477 | private @NotNull BufferedImage concatenateImages(BufferedImage @NotNull [] imageSet) 478 | { 479 | int widthTotal = 0; 480 | for (BufferedImage image : imageSet) widthTotal += image.getWidth(); 481 | 482 | int widthCurr = 0; 483 | BufferedImage concatImage = new BufferedImage(widthTotal, imageSet[0].getHeight(), BufferedImage.TYPE_INT_ARGB); 484 | Graphics2D g2d = concatImage.createGraphics(); 485 | 486 | // Adds the images together. 487 | for (BufferedImage image : imageSet) 488 | { 489 | g2d.drawImage(image, widthCurr, concatImage.getHeight() - image.getHeight(), null); 490 | widthCurr += image.getWidth(); 491 | } 492 | 493 | g2d.dispose(); 494 | 495 | return concatImage; 496 | } 497 | 498 | private Image scale(Image source, int targetWidth, int targetHeight) 499 | { 500 | ImageView imageView = new ImageView(source); 501 | imageView.setPreserveRatio(true); 502 | imageView.setFitWidth(targetWidth); 503 | imageView.setFitHeight(targetHeight); 504 | 505 | SnapshotParameters parameters = new SnapshotParameters(); 506 | parameters.setFill(Color.TRANSPARENT); 507 | 508 | return imageView.snapshot(parameters, null); 509 | } 510 | 511 | public void showPreviewPane() throws Exception 512 | { 513 | if (_neverOpened) 514 | { 515 | createPreviewGIFs(); 516 | loadGIFsIntoViews(); 517 | changePNGPaneColour(); 518 | 519 | Map colourMap = getSpriteColourMap(); 520 | createPalettePanel(colourMap); 521 | 522 | _neverOpened = false; 523 | } 524 | 525 | previewPane.setVisible(true); 526 | } 527 | 528 | private void createPalettePanel(@NotNull Map colourMap) 529 | { 530 | int x = (int) originPane.getLayoutX() + 8; 531 | int y = (int) originPane.getLayoutY() + 10; 532 | 533 | int i = 0; 534 | 535 | boolean switchY = true; 536 | 537 | for (Integer colour : colourMap.keySet()) 538 | { 539 | Rectangle palettePane = new Rectangle(); 540 | 541 | palettePane.setWidth(32); 542 | palettePane.setHeight(32); 543 | palettePane.setLayoutX(x); 544 | palettePane.setLayoutY(y); 545 | 546 | palettePane.getStyleClass().add("colour-pane"); 547 | 548 | Color newColor = Color.valueOf(String.format("#%06X", (0xFFFFFF & colour))); 549 | palettePane.setFill(newColor); 550 | 551 | // Adds the mouse click event handler. 552 | EventHandler eventHandler = this::palettePanelClicked; 553 | palettePane.addEventHandler(MouseEvent.MOUSE_CLICKED, eventHandler); 554 | 555 | colourGroup.getChildren().add(palettePane); 556 | 557 | if (switchY) 558 | { 559 | y += 32; 560 | switchY = false; 561 | } 562 | else 563 | { 564 | y -= 32; 565 | x += 32; 566 | switchY = true; 567 | } 568 | 569 | i++; 570 | if (i >= 14) break; 571 | } 572 | 573 | for (int j = i; j < 14; j++) 574 | { 575 | Rectangle palettePane = new Rectangle(); 576 | 577 | palettePane.setWidth(32); 578 | palettePane.setHeight(32); 579 | palettePane.setLayoutX(x); 580 | palettePane.setLayoutY(y); 581 | 582 | // Default panes. 583 | palettePane.getStyleClass().add("colour-pane"); 584 | palettePane.setFill(Color.BLACK); 585 | 586 | // Adds the mouse click event handler. 587 | EventHandler eventHandler = this::palettePanelClicked; 588 | palettePane.addEventHandler(MouseEvent.MOUSE_CLICKED, eventHandler); 589 | 590 | colourGroup.getChildren().add(palettePane); 591 | 592 | if (switchY) 593 | { 594 | y += 32; 595 | switchY = false; 596 | } 597 | else 598 | { 599 | y -= 32; 600 | x += 32; 601 | switchY = true; 602 | } 603 | } 604 | } 605 | 606 | private void loadGIFsIntoViews() 607 | { 608 | for (Node node : previewPane.getChildren()) 609 | { 610 | if (node instanceof ImageView view && view.getId() != null && !view.getId().contains("-2")) 611 | { 612 | File imageFile = new File(view.getId() + ".gif"); 613 | Image image = new Image(imageFile.toURI().toString()); 614 | 615 | if (!imageFile.delete()) System.out.println("Couldn't delete file."); 616 | 617 | view.setImage(image); 618 | } 619 | } 620 | } 621 | 622 | private @NotNull Map getSpriteColourMap() 623 | { 624 | Map colourMap = new HashMap<>(); 625 | 626 | // Adds all pixel colours to the colour map. 627 | for (Node node: rightPane.getChildren()) 628 | { 629 | if (node instanceof ImageView view && view.getId() != null && view.getImage() != null) 630 | { 631 | int[] data = ( (DataBufferInt) SwingFXUtils.fromFXImage(view.getImage(), null).getRaster().getDataBuffer() ).getData(); 632 | for (int datum : data) putColourInMap(colourMap, datum); 633 | } 634 | } 635 | 636 | return colourMap; 637 | } 638 | 639 | private void putColourInMap(@NotNull Map colourMap, int datum) 640 | { 641 | int rgba = new java.awt.Color(datum).getRGB(); 642 | 643 | Integer colourCount = colourMap.get(rgba); 644 | colourCount = colourCount == null ? 1 : colourCount + 1; 645 | colourMap.put(rgba, colourCount); 646 | } 647 | 648 | private void createPreviewGIFs() throws Exception 649 | { 650 | int delay = 200; 651 | int runDelay = (int) (delay / 1.5); 652 | 653 | int[] delays = new int[] { delay, delay, delay, delay }; 654 | int[] runDelays = new int[] { runDelay, runDelay, runDelay, runDelay }; 655 | 656 | // Walking GIFs. 657 | createDirectionGIF(delays, "walking", "front", "anim-walk-front.gif", true); 658 | createDirectionGIF(delays, "walking", "back", "anim-walk-back.gif", true); 659 | createDirectionGIF(delays, "walking", "left", "anim-walk-left.gif", false); 660 | createDirectionGIF(delays, "walking", "right", "anim-walk-right.gif", false); 661 | 662 | // Running GIFs. 663 | createDirectionGIF(runDelays, "running", "front", "anim-run-front.gif", true); 664 | createDirectionGIF(runDelays, "running", "back", "anim-run-back.gif", true); 665 | createDirectionGIF(runDelays, "running", "left", "anim-run-left.gif", false); 666 | createDirectionGIF(runDelays, "running", "right", "anim-run-right.gif", false); 667 | } 668 | 669 | private @NonNls void createDirectionGIF(int[] delays, String action, String direction, String file, boolean mirror) throws Exception 670 | { 671 | BufferedImage[] sprites = new BufferedImage[mirror ? 4 : 2]; 672 | populateSpriteArray(action, direction, mirror, sprites); 673 | 674 | AnimatedGIFWriter writer = new AnimatedGIFWriter(true); 675 | OutputStream os = new FileOutputStream(file); 676 | 677 | writer.writeAnimatedGIF(sprites, delays, os); 678 | } 679 | 680 | private void populateSpriteArray(String action, String direction, boolean mirror, BufferedImage[] sprites) 681 | { 682 | int i = 0; 683 | 684 | for (Node node : rightPane.getChildren()) 685 | { 686 | if (node instanceof ImageView view && view.getId() != null && view.getId().contains(action) && view.getId().contains(direction)) 687 | { 688 | BufferedImage sprite = SwingFXUtils.fromFXImage(view.getImage(), null); 689 | 690 | sprites[i] = sprite; 691 | 692 | if (mirror) 693 | { 694 | sprite = mirrorImage(sprite); 695 | sprites[i + 2] = sprite; 696 | } 697 | 698 | i++; 699 | } 700 | } 701 | } 702 | 703 | private BufferedImage mirrorImage(@NotNull BufferedImage img) 704 | { 705 | AffineTransform tx = AffineTransform.getScaleInstance(-1, 1); 706 | tx.translate(-img.getWidth(), 0); 707 | 708 | AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); 709 | 710 | return op.filter(img, null); 711 | } 712 | 713 | public void palettePanelClicked(@NotNull MouseEvent e) 714 | { 715 | Rectangle button = (Rectangle) e.getSource(); 716 | _fromColours.add(fxToAWTColor((Color) button.getFill())); 717 | 718 | createColourSelector(new Vector2((int) button.getLayoutX(), (int) button.getLayoutY()), fxToAWTColor((Color) button.getFill())); 719 | 720 | changeColour(); 721 | } 722 | 723 | private void createColourSelector(@NotNull Vector2 position, java.awt.@NotNull Color colour) 724 | { 725 | ImageView view = new ImageView(); 726 | view.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream("img/player-skin/frame-selection.png")))); 727 | 728 | view.setLayoutX(position.x); 729 | view.setLayoutY(position.y); 730 | 731 | view.setId(String.valueOf(colour.getRGB())); 732 | view.getStyleClass().add("colour-pane"); 733 | 734 | EventHandler eventHandler = this::removeFromSelectedColours; 735 | view.setOnMouseClicked(eventHandler); 736 | 737 | colourGroup.getChildren().add(view); 738 | 739 | view.toFront(); 740 | } 741 | 742 | private void removeFromSelectedColours(@NotNull MouseEvent e) 743 | { 744 | ImageView view = (ImageView) e.getSource(); 745 | int code = Integer.parseInt(view.getId()); 746 | java.awt.Color toRemove = new java.awt.Color(code); 747 | 748 | _fromColours.remove(toRemove); 749 | colourGroup.getChildren().remove(view); 750 | 751 | changeColour(); 752 | } 753 | 754 | private void changeColour() 755 | { 756 | ArrayList originalImages = changeTemporaryPaneColour(_toColour); 757 | 758 | try 759 | { 760 | changePNGPaneColour(); 761 | createPreviewGIFs(); 762 | loadGIFsIntoViews(); 763 | } 764 | catch (Exception ex) 765 | { 766 | throw new RuntimeException(ex); 767 | } 768 | finally 769 | { 770 | revertTemporaryChanges(originalImages); 771 | } 772 | } 773 | 774 | private void changePNGPaneColour() 775 | { 776 | for (Node node : previewPane.getChildren()) 777 | { 778 | if (node instanceof ImageView view && view.getId() != null && view.getId().contains("-2") && !view.getId().contains("pane")) 779 | { 780 | view.setImage(((ImageView) rightPane.lookup("#" + view.getId().replace("-2", ""))).getImage()); 781 | } 782 | } 783 | } 784 | 785 | private @NotNull ArrayList changeTemporaryPaneColour(java.awt.Color to) 786 | { 787 | ArrayList oldImageList = new ArrayList<>(); 788 | 789 | for (Node node : rightPane.getChildren()) 790 | { 791 | if (node instanceof ImageView view && view.getId() != null) 792 | { 793 | Image oldImage = view.getImage(); 794 | oldImageList.add(oldImage); 795 | 796 | BufferedImage convertedImage = SwingFXUtils.fromFXImage(oldImage, null); 797 | 798 | for (java.awt.Color fromColour : _fromColours) 799 | { 800 | BufferedImageOp lookup = new LookupOp(new ColorMapper(fromColour, to), null); 801 | convertedImage = lookup.filter(convertedImage, null); 802 | } 803 | 804 | if (convertedImage != null) view.setImage(SwingFXUtils.toFXImage(convertedImage, null)); 805 | } 806 | } 807 | 808 | return oldImageList; 809 | } 810 | 811 | private void revertTemporaryChanges(ArrayList originalImages) 812 | { 813 | for (Node node : rightPane.getChildren()) 814 | { 815 | if (node instanceof ImageView view && view.getId() != null) 816 | { 817 | view.setImage(originalImages.get(0)); 818 | originalImages.remove(0); 819 | } 820 | } 821 | } 822 | 823 | public void setReplacementColour(@NotNull MouseEvent e) 824 | { 825 | Rectangle colourRect = (Rectangle) e.getSource(); 826 | Color fxColour = (Color) colourRect.getFill(); 827 | 828 | toPicker.setLayoutX(colourRect.getLayoutX()); 829 | toPicker.setLayoutY(colourRect.getLayoutY()); 830 | 831 | _toColour = new java.awt.Color((float) fxColour.getRed(), (float) fxColour.getGreen(), (float) fxColour.getBlue(), (float) fxColour.getOpacity()); 832 | 833 | if (_fromColours.size() == 0) return; 834 | changeColour(); 835 | } 836 | 837 | public void hidePreviewPane() 838 | { 839 | previewPane.setVisible(false); 840 | } 841 | 842 | @Contract("_ -> new") 843 | private java.awt.@NotNull Color fxToAWTColor(@NotNull Color fxColour) 844 | { 845 | return new java.awt.Color((float) fxColour.getRed(), (float) fxColour.getGreen(), (float) fxColour.getBlue(), (float) fxColour.getOpacity()); 846 | } 847 | } -------------------------------------------------------------------------------- /src/main/java/mayaseii/wildsmoddingtool/Vector2.java: -------------------------------------------------------------------------------- 1 | package mayaseii.wildsmoddingtool; 2 | 3 | public class Vector2 4 | { 5 | public final static Vector2 Zero = new Vector2(); 6 | 7 | public double x; 8 | 9 | public double y; 10 | 11 | public Vector2() 12 | { 13 | this.x = 0; 14 | this.y = 0; 15 | } 16 | 17 | public Vector2(int x, int y) 18 | { 19 | this.x = x; 20 | this.y = y; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module mayaseii.wildsmoddingtool { 2 | requires javafx.controls; 3 | requires javafx.fxml; 4 | requires javafx.media; 5 | requires javafx.web; 6 | requires javafx.swing; 7 | 8 | requires org.controlsfx.controls; 9 | requires com.dlsc.formsfx; 10 | requires validatorfx; 11 | requires org.kordamp.ikonli.javafx; 12 | requires org.kordamp.bootstrapfx.core; 13 | requires eu.hansolo.tilesfx; 14 | requires java.datatransfer; 15 | requires java.desktop; 16 | requires org.jetbrains.annotations; 17 | 18 | opens mayaseii.wildsmoddingtool to javafx.fxml; 19 | exports mayaseii.wildsmoddingtool; 20 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: mayaseii.wildsmoddingtool.App 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/PokeWilds-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/PokeWilds-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/app.css: -------------------------------------------------------------------------------- 1 | .root { 2 | 3 | -fx-font-family: "PokeWilds"; 4 | -fx-font-size: 16px; 5 | 6 | } 7 | 8 | .label { 9 | 10 | -fx-font-family: "PokeWilds"; 11 | -fx-text-fill: black; 12 | 13 | } 14 | 15 | .button { 16 | 17 | -fx-padding: 0 28px; 18 | -fx-background-color: transparent; 19 | -fx-background-image: url("img/buttonBG.png"); 20 | -fx-border: 0px; 21 | -fx-text-fill: black; 22 | 23 | } 24 | 25 | .button:hover { 26 | 27 | -fx-background-color: #a8a8c8; 28 | -fx-cursor: hand; 29 | 30 | } 31 | 32 | #container { 33 | 34 | -fx-background-image: url("img/background.png"); 35 | 36 | } 37 | 38 | #seii-span { 39 | 40 | -fx-font-size: 16px; 41 | -fx-font-family: "PokeWilds"; 42 | -fx-text-fill: #f83808; 43 | -fx-cursor: hand; 44 | 45 | } 46 | 47 | .hyperlink { 48 | -fx-border-color: transparent; 49 | } 50 | 51 | #btn-pokemon { 52 | -fx-alignment: center-right; 53 | -fx-background-image: url("img/buttonPokemonBG.png"); 54 | } 55 | 56 | #btn-texture { 57 | -fx-alignment: center-right; 58 | -fx-background-image: url("img/buttonTextureBG.png"); 59 | } 60 | 61 | #btn-resource { 62 | -fx-alignment: center-right; 63 | -fx-background-image: url("img/buttonResourceBG.png"); 64 | } 65 | 66 | #btn-biome { 67 | -fx-alignment: center-left; 68 | -fx-background-image: url("img/buttonBiomeBG.png"); 69 | } 70 | 71 | #btn-music { 72 | -fx-alignment: center-left; 73 | -fx-background-image: url("img/buttonMusicBG.png"); 74 | } 75 | 76 | #btn-skin { 77 | -fx-alignment: center-left; 78 | -fx-background-image: url("img/buttonPlayerBG.png"); 79 | } 80 | 81 | #volume:hover { 82 | -fx-cursor: hand; 83 | } -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/audio/BG_AzaleaTown.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/audio/BG_AzaleaTown.mp3 -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/audio/BG_AzaleaTown.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/audio/BG_AzaleaTown.wav -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/audio/BG_VioletCity.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/audio/BG_VioletCity.wav -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/audio/click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/audio/click.wav -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/background.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonBiomeBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonBiomeBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonMusicBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonMusicBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonPlayerBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonPlayerBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonPokemonBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonPokemonBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonResourceBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonResourceBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/buttonTextureBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/buttonTextureBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/noVolume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/noVolume.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/fishing-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/fishing-back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/fishing-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/fishing-front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/fishing-side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/fishing-side.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-colours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-colours.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-error.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-large.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-long.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-medium-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-medium-long.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-medium.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-selection.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-short.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-xlarge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame-xlarge.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/frame.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/generate-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/generate-btn.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-left.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/idle-right.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/name-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/name-bg.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/preview-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/preview-bg.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/preview-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/preview-btn.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-left.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-idle-right.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-left.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/run-right.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sitting-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sitting-back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sitting-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sitting-front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sitting-side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sitting-side.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sleeping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/sleeping.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-back.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-front.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-left.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/player-skin/walk-right.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/toolbarBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/toolbarBG.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/img/volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaSeii/wilds-modding-tool/54922e0db38d2ee582651ecb08b4f2cfc1ad0617/src/main/resources/mayaseii/wildsmoddingtool/img/volume.png -------------------------------------------------------------------------------- /src/main/resources/mayaseii/wildsmoddingtool/main-menu.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |