diff --git a/src/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandler.java b/src/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandler.java index 4106a8e76..81a92200e 100644 --- a/src/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandler.java +++ b/src/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandler.java @@ -32,389 +32,383 @@ /** * Offers an interface to import Aseprite JSON export format. * Note: requires animation key frames to have same dimensions to support internal animation format. - * */ + */ public class AsepriteHandler { - - /** + + /** * Thrown to indicate error when importing Aseprite JSON format. - * */ + */ public static class ImportAnimationException extends Error { public ImportAnimationException(String message) { super(message); } } - - /** + + /** * Imports an Aseprite animation (.json + sprite sheet). * * @param jsonPath path (including filename) to Aseprite JSON. - * * @return Animation object represented by each key frame in Aseprite sprite sheet. - * */ + */ public static Animation importAnimation(String jsonPath) throws IOException, FileNotFoundException, AsepriteHandler.ImportAnimationException { - + JsonElement rootElement = null; - try { rootElement = getRootJsonElement(jsonPath); } - catch(FileNotFoundException e) { + try { + rootElement = getRootJsonElement(jsonPath); + } catch (FileNotFoundException e) { throw new FileNotFoundException("FileNotFoundException: Could not find .json file " + jsonPath); } - + File spriteSheetFile = null; - try { spriteSheetFile = getSpriteSheetFile(rootElement, jsonPath); } - catch(FileNotFoundException e) { + try { + spriteSheetFile = getSpriteSheetFile(rootElement, jsonPath); + } catch (FileNotFoundException e) { throw new FileNotFoundException("FileNotFoundException: Could not find sprite sheet file. " + - "Expected location is 'image' in .json metadata, or same folder as .json file."); + "Expected location is 'image' in .json metadata, or same folder as .json file."); } - + Dimension keyFrameDimensions = getKeyFrameDimensions(rootElement); - if(areKeyFramesSameDimensions(rootElement, keyFrameDimensions)) { - + if (areKeyFramesSameDimensions(rootElement, keyFrameDimensions)) { + BufferedImage image = null; - try { image = ImageIO.read(spriteSheetFile); } - catch(IOException e) { + try { + image = ImageIO.read(spriteSheetFile); + } catch (IOException e) { throw new IOException("IOException: Could not write sprite sheet data to BufferedImage object."); } - + Spritesheet spriteSheet = new Spritesheet(image, - spriteSheetFile.getPath().toString(), - (int)keyFrameDimensions.getWidth(), - (int)keyFrameDimensions.getHeight()); - + spriteSheetFile.getPath().toString(), + (int) keyFrameDimensions.getWidth(), + (int) keyFrameDimensions.getHeight()); + return new Animation(spriteSheet, false, getKeyFrameDurations(rootElement)); } - + throw new AsepriteHandler.ImportAnimationException("AsepriteHandler.ImportAnimationException: animation key frames require same dimensions."); } - + /** * @param jsonPath path (including filename) to Aseprite .json file. - * * @return root element of JSON data. - * */ + */ private static JsonElement getRootJsonElement(String jsonPath) throws FileNotFoundException { - + File jsonFile = new File(jsonPath); - - try { - JsonElement rootElement = JsonParser.parseReader(new FileReader(jsonFile)); + + try { + JsonElement rootElement = JsonParser.parseReader(new FileReader(jsonFile)); return rootElement; + } catch (FileNotFoundException e) { + throw e; } - catch(FileNotFoundException e) { throw e; } } - + /** - * Searches for sprite sheet path through .json metadata, or same folder as .json file. * @param rootElement root element of JSON data. - * @param jsonPath path (including filename) to .json Aseprite file. + * @return path (including filename) to animation sprite sheet. + */ + private static String getSpriteSheetPath(JsonElement rootElement) { + + JsonElement metaData = rootElement.getAsJsonObject().get("meta"); + String spriteSheetPath = metaData.getAsJsonObject().get("image").getAsString(); + + return spriteSheetPath; + } + + /** + * Searches for sprite sheet path through .json metadata and same folder as .json file. * + * @param rootElement root element of JSON data. + * @param jsonPath path (including filename) to .json Aseprite file. * @return sprite sheet file if it can be found, else an exception is thrown. - * */ + */ private static File getSpriteSheetFile(JsonElement rootElement, String jsonPath) throws FileNotFoundException { - + //try searching path supplied in .json data JsonElement metaData = rootElement.getAsJsonObject().get("meta"); String spriteSheetPath = metaData.getAsJsonObject().get("image").getAsString(); - + File spriteSheetFile = new File(spriteSheetPath); - - if(spriteSheetFile.exists()) + + if (spriteSheetFile.exists()) return spriteSheetFile; - + //try searching local directory Path jsonFilePath = Paths.get(jsonPath); String dirPath = jsonFilePath.getParent().toString(); String fileName1 = jsonFilePath.getFileName().toString(); String alternative1 = fileName1.substring(0, fileName1.lastIndexOf(".")); //same file name as .json - + Path spriteSheetFilePath = Paths.get(spriteSheetPath); String fileName2 = spriteSheetFilePath.getFileName().toString(); String alternative2 = fileName2.substring(0, fileName2.lastIndexOf(".")); //same file name as 'image' element - + List suffixes = Arrays.asList(".png", ".jpg", ".jpeg"); - for(String suffix : suffixes) { - + for (String suffix : suffixes) { + String alternativeFile1 = dirPath + "/" + alternative1 + suffix; spriteSheetFile = new File(alternativeFile1); - if(spriteSheetFile.exists()) + if (spriteSheetFile.exists()) return spriteSheetFile; - + String alternativeFile2 = dirPath + "/" + alternative2 + suffix; spriteSheetFile = new File(alternativeFile2); - if(spriteSheetFile.exists()) + if (spriteSheetFile.exists()) return spriteSheetFile; } - + throw new FileNotFoundException(); } - - /** - * @param rootElement root element of JSON data. - * - * @return path (including filename) to animation sprite sheet. - * */ - private static String getSpriteSheetPath(JsonElement rootElement) { - - JsonElement metaData = rootElement.getAsJsonObject().get("meta"); - String spriteSheetPath = metaData.getAsJsonObject().get("image").getAsString(); - - return spriteSheetPath; - } - + /** * @param rootElement root element of JSON data. - * * @return dimensions of first key frame. - * */ + */ private static Dimension getKeyFrameDimensions(JsonElement rootElement) { - + JsonElement frames = rootElement.getAsJsonObject().get("frames"); - + JsonObject firstFrameObject = frames.getAsJsonObject().entrySet().iterator().next().getValue().getAsJsonObject(); JsonObject frameDimensions = firstFrameObject.get("sourceSize").getAsJsonObject(); - + int frameWidth = frameDimensions.get("w").getAsInt(); int frameHeight = frameDimensions.get("h").getAsInt(); - + return new Dimension(frameWidth, frameHeight); } - + /** * @param rootElement root element of JSON data. - * @param expected expected dimensions of each key frame. - * + * @param expected expected dimensions of each key frame. * @return true if key frames have same duration. - * */ + */ private static boolean areKeyFramesSameDimensions(JsonElement rootElement, Dimension expected) { - + JsonElement frames = rootElement.getAsJsonObject().get("frames"); - - for(Map.Entry entry : frames.getAsJsonObject().entrySet()) { + + for (Map.Entry entry : frames.getAsJsonObject().entrySet()) { JsonObject frameObject = entry.getValue().getAsJsonObject(); JsonObject frameDimensions = frameObject.get("sourceSize").getAsJsonObject(); - + int frameWidth = frameDimensions.get("w").getAsInt(); int frameHeight = frameDimensions.get("h").getAsInt(); - - if(frameWidth != expected.getWidth() || frameHeight != expected.getHeight()) + + if (frameWidth != expected.getWidth() || frameHeight != expected.getHeight()) return false; } - + return true; } - + /** * @param rootElement root element of JSON data. - * * @return integer array representing duration of each key frame. - * */ + */ public static int[] getKeyFrameDurations(JsonElement rootElement) { - + JsonElement frames = rootElement.getAsJsonObject().get("frames"); - + Set> keyFrameSet = frames.getAsJsonObject().entrySet(); - + int[] keyFrameDurations = new int[keyFrameSet.size()]; - + int frameIndex = 0; - for(Map.Entry entry : keyFrameSet) { + for (Map.Entry entry : keyFrameSet) { JsonObject frameObject = entry.getValue().getAsJsonObject(); int frameDuration = frameObject.get("duration").getAsInt(); keyFrameDurations[frameIndex++] = frameDuration; } - + return keyFrameDurations; } - - /** - * Error that is thrown by the export class - */ - public static class ExportAnimationException extends Error { - public ExportAnimationException(String message) { - super(message); - } + + /** + * Error that is thrown by the export class + */ + public static class ExportAnimationException extends Error { + public ExportAnimationException(String message) { + super(message); } - - /** - * Creates the json representation of an animation object and returns it. - * This is the public accesible function and can/should be changed to fit into the UI. - * - * @param spritesheetResource the animation object to export - */ - public String exportAnimation(SpritesheetResource spritesheetResource){ - - String json = createJson(spritesheetResource); - return json; + } + + /** + * Creates the json representation of an animation object and returns it. + * This is the public accesible function and can/should be changed to fit into the UI. + * + * @param spritesheetResource the animation object to export + */ + public static String exportAnimation(SpritesheetResource spritesheetResource) { + String json = createJson(spritesheetResource); + return json; + } + + /** + * Creates the json representation of an animation object and returns it as a string. + * + * @param spritesheetResource spritesheetResource object to export as json. + * @return the json as a string. + */ + private static String createJson(SpritesheetResource spritesheetResource) { + Spritesheet spritesheet = Resources.spritesheets().load(spritesheetResource); + assert spritesheet != null; + int[] keyframes = Resources.spritesheets().getCustomKeyFrameDurations(spritesheet); + Frames[] frames = new Frames[keyframes.length]; + + if (frames.length != spritesheet.getTotalNumberOfSprites()) { + throw new ExportAnimationException("Different dimensions of keyframes and sprites in spritesheet"); } - - /** - * Creates the json representation of an animation object and returns it as a string. - * - * @param spritesheetResource spritesheetResource object to export as json. - * @return the json as a string. - */ - private String createJson(SpritesheetResource spritesheetResource){ - Spritesheet spritesheet = Resources.spritesheets().load(spritesheetResource); - assert spritesheet != null; - int[] keyframes = Resources.spritesheets().getCustomKeyFrameDurations(spritesheet); - Frames[] frames = new Frames[keyframes.length]; - - if(frames.length != spritesheet.getTotalNumberOfSprites()){ - throw new ExportAnimationException("Different dimensions of keyframes and sprites in spritesheet"); - } - - // Build the frames object in the json - int numCol = spritesheet.getColumns(); - int numRows = spritesheet.getRows(); - int frameWidth = spritesheet.getSpriteWidth(); - int frameHeight = spritesheet.getSpriteHeight(); - - for(int i = 0; i < numRows; i++){ - for(int j = 0; j < numCol; j++){ - final int row = i; - final int col = j; - Map frame = new HashMap<>(){{ - put("x", (0 + col*frameWidth) ); - put("y", (0 + row*frameHeight) ); - put("w", frameWidth); - put("h", frameHeight); - }}; - Map spriteSourceSize = new HashMap<>(){{ - put("x", 0); - put("y", 0); - put("w", frameWidth); - put("h", frameHeight); - }}; - Map sourceSize = new HashMap<>(){{ - put("w", frameWidth); - put("h", frameHeight); - }}; - int duration = keyframes[i+j]; - String index = String.valueOf(i+j); - frames[i+j] = new Frames("frame " + index, - frame, - false, - false, - spriteSourceSize, - sourceSize, - duration); - } - } - - // Build the meta object in the json - int spritesheetWidth = frameWidth * numCol; - int spritesheetHeight = frameHeight * numRows; - Map size= new HashMap<>(){{ - put("w", spritesheetWidth); - put("h", spritesheetHeight); + + // Build the frames object in the json + int numCol = spritesheet.getColumns(); + int numRows = spritesheet.getRows(); + int frameWidth = spritesheet.getSpriteWidth(); + int frameHeight = spritesheet.getSpriteHeight(); + + for (int i = 0; i < numRows; i++) { + for (int j = 0; j < numCol; j++) { + final int row = i; + final int col = j; + Map frame = new HashMap<>() {{ + put("x", (0 + col * frameWidth)); + put("y", (0 + row * frameHeight)); + put("w", frameWidth); + put("h", frameHeight); }}; - String spritesheetName = spritesheet.getName(); - Layer[] layers = {new Layer("Layer",255,"normal")}; - Meta meta = new Meta("http://www.aseprite.org/", - "1.2.16.3-x64", - spritesheetName, - "RGBA8888", size, "1", layers); - - // Create the json as string - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - StringBuilder sb = new StringBuilder(); - - sb.append("{ \"frames\": {\n"); - for(int i = 0; i < frames.length; i++){ - String json = gson.toJson(frames[i]); - sb.append(" \"" + frames[i].name + "\": ").append(json).append(",\n"); - } - sb.append(" },\n"); - String json = gson.toJson(meta); - sb.append("\"meta\":").append(json).append("\n}"); - - return sb.toString(); + Map spriteSourceSize = new HashMap<>() {{ + put("x", 0); + put("y", 0); + put("w", frameWidth); + put("h", frameHeight); + }}; + Map sourceSize = new HashMap<>() {{ + put("w", frameWidth); + put("h", frameHeight); + }}; + int duration = keyframes[i + j]; + String index = String.valueOf(i + j); + frames[i + j] = new Frames("frame " + index, + frame, + false, + false, + spriteSourceSize, + sourceSize, + duration); + } } - + + // Build the meta object in the json + int spritesheetWidth = frameWidth * numCol; + int spritesheetHeight = frameHeight * numRows; + Map size = new HashMap<>() {{ + put("w", spritesheetWidth); + put("h", spritesheetHeight); + }}; + String spritesheetName = spritesheet.getName(); + Layer[] layers = {new Layer("Layer", 255, "normal")}; + Meta meta = new Meta("http://www.aseprite.org/", + "1.2.16.3-x64", + spritesheetName, + "RGBA8888", size, "1", layers); + + // Create the json as string + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + StringBuilder sb = new StringBuilder(); + + sb.append("{ \"frames\": {\n"); + for (int i = 0; i < frames.length; i++) { + String json = gson.toJson(frames[i]); + sb.append(" \"" + frames[i].name + "\": ").append(json).append(",\n"); + } + sb.append(" },\n"); + String json = gson.toJson(meta); + sb.append("\"meta\":").append(json).append("\n}"); + + return sb.toString(); + } + + /** + * Frames class for Aseprite json structure. + */ + private static class Frames { + transient String name; + Map frame; + boolean rotated; + boolean trimmed; + Map spriteSourceSize; + Map sourceSize; + int duration; + /** - * Frames class for Aseprite json structure. + * @param name name of frame + * @param frame x, y, w, h on the substruction of the sprite in the spritesheet. + * @param rotated is the frame rotated? + * @param trimmed is the frame trimmed? + * @param spriteSourceSize how the sprite is trimmed. + * @param sourceSize the original sprite size. + * @param duration the duration of the frame */ - private class Frames { - transient String name; - Map frame; - boolean rotated; - boolean trimmed; - Map spriteSourceSize; - Map sourceSize; - int duration; - - /** - * - * @param name name of frame - * @param frame x, y, w, h on the substruction of the sprite in the spritesheet. - * @param rotated is the frame rotated? - * @param trimmed is the frame trimmed? - * @param spriteSourceSize how the sprite is trimmed. - * @param sourceSize the original sprite size. - * @param duration the duration of the frame - */ - public Frames(String name, Map frame, boolean rotated, boolean trimmed, Map spriteSourceSize, Map sourceSize, int duration){ - this.name = name; - this.frame = frame; - this.rotated = rotated; - this.trimmed = trimmed; - this.spriteSourceSize = spriteSourceSize; - this.sourceSize = sourceSize; - this.duration = duration; - } + public Frames(String name, Map frame, boolean rotated, boolean trimmed, Map spriteSourceSize, Map sourceSize, int duration) { + this.name = name; + this.frame = frame; + this.rotated = rotated; + this.trimmed = trimmed; + this.spriteSourceSize = spriteSourceSize; + this.sourceSize = sourceSize; + this.duration = duration; } - + } + + /** + * Meta data class for Aseprite json structure. + */ + private static class Meta { + String app; + String version; + String image; + String format; + Map size; + String scale; + Layer[] layers; + /** - * Meta data class for Aseprite json structure. + * @param app the application the json format comes from, in this case Aseprite. + * @param version Version of application. + * @param image filename of spritesheet. + * @param format color format of spritesheet image. + * @param size Size of spritesheet. + * @param scale Scale of spritesheet. + * @param layers Layers of spritesheet. */ - private class Meta { - String app; - String version; - String image; - String format; - Map size; - String scale; - Layer[] layers; - - /** - * - * @param app the application the json format comes from, in this case Aseprite. - * @param version Version of application. - * @param image filename of spritesheet. - * @param format color format of spritesheet image. - * @param size Size of spritesheet. - * @param scale Scale of spritesheet. - * @param layers Layers of spritesheet. - */ - public Meta(String app, String version, String image, String format, Map size, String scale, Layer[] layers){ - this.app = app; - this.version = version; - this.image = image; - this.format = format; - this.size = size; - this. scale = scale; - this.layers = layers; - } + public Meta(String app, String version, String image, String format, Map size, String scale, Layer[] layers) { + this.app = app; + this.version = version; + this.image = image; + this.format = format; + this.size = size; + this.scale = scale; + this.layers = layers; } + } + + /** + * Layer class for Aseprite json structure. + */ + private static class Layer { + String name; + int opacity; + String blendMode; /** - * Layer class for Aseprite json structure. + * @param name Name of layer. + * @param opacity Opacity level of layer. + * @param blendMode Blendmode of layer. */ - private class Layer { - String name; - int opacity; - String blendMode; - - /** - * - * @param name Name of layer. - * @param opacity Opacity level of layer. - * @param blendMode Blendmode of layer. - */ - public Layer(String name, int opacity, String blendMode){ - this.name = name; - this.opacity = opacity; - this.blendMode = blendMode; - } - - } + public Layer(String name, int opacity, String blendMode) { + this.name = name; + this.opacity = opacity; + this.blendMode = blendMode; + } + + } } diff --git a/tests/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandlerTests.java b/tests/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandlerTests.java index a330552c4..0961fb48c 100644 --- a/tests/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandlerTests.java +++ b/tests/de/gurkenlabs/litiengine/graphics/animation/AsepriteHandlerTests.java @@ -15,9 +15,10 @@ import de.gurkenlabs.litiengine.resources.ImageFormat; import de.gurkenlabs.litiengine.resources.SpritesheetResource; +import static org.junit.jupiter.api.Assertions.*; public class AsepriteHandlerTests { - + /** * Tests that Aseprite animation import works as expected when given valid input. */ @@ -27,9 +28,9 @@ public void importAsepriteAnimationTest() { Animation animation = AsepriteHandler.importAnimation("tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0001.json"); assertEquals("Sprite-0001-sheet", animation.getName()); assertEquals(300, animation.getTotalDuration()); - for(int keyFrameDuration : animation.getKeyFrameDurations()) + for (int keyFrameDuration : animation.getKeyFrameDurations()) assertEquals(100, keyFrameDuration); - + Spritesheet spriteSheet = animation.getSpritesheet(); assertEquals(32, spriteSheet.getSpriteHeight()); assertEquals(32, spriteSheet.getSpriteWidth()); @@ -37,55 +38,52 @@ public void importAsepriteAnimationTest() { assertEquals(1, spriteSheet.getRows()); assertEquals(3, spriteSheet.getColumns()); assertEquals(ImageFormat.PNG, spriteSheet.getImageFormat()); - + BufferedImage image = spriteSheet.getImage(); assertEquals(96, image.getWidth()); assertEquals(32, image.getHeight()); - } - catch(FileNotFoundException e) { + } catch (FileNotFoundException e) { fail(e.getMessage()); - } - catch(IOException e) { + } catch (IOException e) { fail(e.getMessage()); - } - catch(AsepriteHandler.ImportAnimationException e) { + } catch (AsepriteHandler.ImportAnimationException e) { fail(e.getMessage()); } } - + /** * Test that if AsepriteHandler.ImportAnimationException will be throwed if different frame dimensions are provided. */ @Test public void ImportAnimationExceptionTest() { - - Throwable exception = assertThrows(ImportAnimationException.class, () -> AsepriteHandler.importAnimation("tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0002.json")); - assertEquals("AsepriteHandler.ImportAnimationException: animation key frames require same dimensions.", exception.getMessage()); + + Throwable exception = assertThrows(ImportAnimationException.class, () -> AsepriteHandler.importAnimation("tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0002.json")); + assertEquals("AsepriteHandler.ImportAnimationException: animation key frames require same dimensions.", exception.getMessage()); } /** * Tests thrown FileNotFoundException when importing an Aseprite animation. - * + *

* 1.first, we test if FileNotFoundException would be throwed if .json file cannot be found. * 2.then we test if FileNotFoundException would be throwed if spritesheet file cannot be found. */ @Test - public void FileNotFoundExceptionTest(){ + public void FileNotFoundExceptionTest() { Throwable exception_withoutJsonFile = assertThrows(FileNotFoundException.class, () -> AsepriteHandler.importAnimation("tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0003.json")); assertEquals("FileNotFoundException: Could not find .json file tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0003.json", exception_withoutJsonFile.getMessage()); Throwable exception_withoutSpriteSheet = assertThrows(FileNotFoundException.class, () -> AsepriteHandler.importAnimation("tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0004.json")); assertEquals("FileNotFoundException: Could not find sprite sheet file. Expected location is 'image' in .json metadata, or same folder as .json file.", exception_withoutSpriteSheet.getMessage()); } - + /** - * Test that just create a json and prints in to standard output. + * Test that just create a json and prints in to standard output. */ @Test public void exportAnimationTest() { String spritesheetPath = "tests/de/gurkenlabs/litiengine/graphics/animation/aseprite_test_animations/Sprite-0001-sheet.png"; BufferedImage image = new BufferedImage(96, 32, BufferedImage.TYPE_4BYTE_ABGR); Spritesheet spritesheet = new Spritesheet(image, spritesheetPath, 32, 32); - Animation animation = new Animation(spritesheet, false, false, 2,2,2); + Animation animation = new Animation(spritesheet, false, false, 2, 2, 2); int[] keyFrames = animation.getKeyFrameDurations(); SpritesheetResource spritesheetResource = new SpritesheetResource(animation.getSpritesheet()); spritesheetResource.setKeyframes(keyFrames); diff --git a/utiliti/localization/strings.properties b/utiliti/localization/strings.properties index 3f08e7d9b..d875be8e4 100644 --- a/utiliti/localization/strings.properties +++ b/utiliti/localization/strings.properties @@ -58,6 +58,7 @@ menu_assets_importBlueprints=Import Blueprints... menu_assets_importTilesets=Import Tilesets... menu_assets_importSounds=Import Sounds... menu_assets_editSprite=Edit Spritesheet(s) +menu_assets_importAnimation=Import Animation... menu_help=Help menu_help_utiliti=utiLITI diff --git a/utiliti/src/de/gurkenlabs/utiliti/components/Editor.java b/utiliti/src/de/gurkenlabs/utiliti/components/Editor.java index d95dfdb15..d5c014810 100644 --- a/utiliti/src/de/gurkenlabs/utiliti/components/Editor.java +++ b/utiliti/src/de/gurkenlabs/utiliti/components/Editor.java @@ -38,6 +38,8 @@ import de.gurkenlabs.litiengine.environment.tilemap.xml.TmxMap; import de.gurkenlabs.litiengine.graphics.Spritesheet; import de.gurkenlabs.litiengine.graphics.TextRenderer; +import de.gurkenlabs.litiengine.graphics.animation.Animation; +import de.gurkenlabs.litiengine.graphics.animation.AsepriteHandler; import de.gurkenlabs.litiengine.graphics.emitters.xml.EmitterData; import de.gurkenlabs.litiengine.graphics.emitters.xml.EmitterLoader; import de.gurkenlabs.litiengine.gui.screens.Screen; @@ -77,6 +79,7 @@ public class Editor extends Screen { private static final String TEXTUREATLAS_FILE_NAME = "Texture Atlas XML (generic)"; private static final String IMPORT_DIALOGUE = "import_something"; + private static final String ANIMATION_FILE_NAME = "Animation file"; private static Editor instance; private static UserPreferences preferences; @@ -380,6 +383,12 @@ public void importSpriteSheets() { } } + public void importAnimation() { + if (EditorFileChooser.showFileDialog(ANIMATION_FILE_NAME, Resources.strings().get(IMPORT_DIALOGUE, ANIMATION_FILE_NAME), false, "json") == JFileChooser.APPROVE_OPTION) { + this.processAnimation(EditorFileChooser.instance().getSelectedFile()); + } + } + public void importSounds() { if (EditorFileChooser.showFileDialog(AUDIO_FILE_NAME, Resources.strings().get(IMPORT_DIALOGUE, AUDIO_FILE_NAME), true, SoundFormat.getAllExtensions()) == JFileChooser.APPROVE_OPTION) { this.importSounds(EditorFileChooser.instance().getSelectedFiles()); @@ -488,6 +497,30 @@ private void processSpritesheets(SpritesheetImportPanel spritePanel) { this.loadSpriteSheets(sprites, true); } + /** + * Loads an animation (spritesheet with keyframes) in to the editor + * @param file - a json file, encoded by the asesprite export standard + */ + public void processAnimation(File file) { + try { + Animation animation = AsepriteHandler.importAnimation(file.getAbsolutePath()); + int[] keyFrames = animation.getKeyFrameDurations(); + SpritesheetResource spritesheetResource = new SpritesheetResource(animation.getSpritesheet()); + spritesheetResource.setKeyframes(keyFrames); + Collection sprites = new ArrayList<>(Collections.singleton(spritesheetResource)); + for (SpritesheetResource info : sprites) { + Resources.spritesheets().getAll().removeIf(x -> x.getName().equals(info.getName() + "-preview")); + this.getGameFile().getSpriteSheets().removeIf(x -> x.getName().equals(info.getName())); + this.getGameFile().getSpriteSheets().add(info); + log.log(Level.INFO, "imported spritesheet {0}", new Object[]{info.getName()}); + } + this.loadSpriteSheets(sprites, true); + + } catch (AsepriteHandler.ImportAnimationException | IOException e) { + log.log(Level.SEVERE, e.getMessage(), e); + } + } + public void importEmitters() { XmlImportDialog.importXml("Emitter", file -> { EmitterData emitter; @@ -793,4 +826,4 @@ private void gamefileLoaded() { callback.run(); } } -} \ No newline at end of file +} diff --git a/utiliti/src/de/gurkenlabs/utiliti/swing/AssetPanelItem.java b/utiliti/src/de/gurkenlabs/utiliti/swing/AssetPanelItem.java index 240fd1b9c..8ceb6e59b 100644 --- a/utiliti/src/de/gurkenlabs/utiliti/swing/AssetPanelItem.java +++ b/utiliti/src/de/gurkenlabs/utiliti/swing/AssetPanelItem.java @@ -11,6 +11,8 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; +import java.io.FileWriter; +import java.io.Writer; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; @@ -48,7 +50,9 @@ import de.gurkenlabs.litiengine.environment.tilemap.xml.MapObject; import de.gurkenlabs.litiengine.environment.tilemap.xml.Tileset; import de.gurkenlabs.litiengine.graphics.Spritesheet; +import de.gurkenlabs.litiengine.graphics.animation.AsepriteHandler; import de.gurkenlabs.litiengine.graphics.emitters.xml.EmitterData; +import de.gurkenlabs.litiengine.graphics.animation.AsepriteHandler; import de.gurkenlabs.litiengine.resources.ImageFormat; import de.gurkenlabs.litiengine.resources.Resources; import de.gurkenlabs.litiengine.resources.SoundFormat; @@ -431,7 +435,7 @@ private void exportSpritesheet() { ImageFormat format = sprite.getImageFormat() != ImageFormat.UNSUPPORTED ? sprite.getImageFormat() : ImageFormat.PNG; - Object[] options = { ".xml", format.toFileExtension() }; + Object[] options = { ".xml", format.toFileExtension(), ".json"}; int answer = JOptionPane.showOptionDialog(Game.window().getRenderComponent(), "Select an export format:", "Export Spritesheet", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]); try { @@ -441,21 +445,48 @@ private void exportSpritesheet() { chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); chooser.setDialogType(JFileChooser.SAVE_DIALOG); chooser.setDialogTitle("Export Spritesheet"); - if (answer == 0) { - XmlExportDialog.export(spriteSheetInfo, "Spritesheet", spriteSheetInfo.getName()); - } else if (answer == 1) { - FileFilter filter = new FileNameExtensionFilter(format.toString() + " - Image", format.toString()); - chooser.setFileFilter(filter); - chooser.addChoosableFileFilter(filter); - chooser.setSelectedFile(new File(spriteSheetInfo.getName() + format.toFileExtension())); - - int result = chooser.showSaveDialog(Game.window().getRenderComponent()); - if (result == JFileChooser.APPROVE_OPTION) { - ImageSerializer.saveImage(chooser.getSelectedFile().toString(), sprite.getImage(), format); - log.log(Level.INFO, "exported spritesheet {0} to {1}", new Object[] { spriteSheetInfo.getName(), chooser.getSelectedFile() }); + switch (answer) { + case 0: { + XmlExportDialog.export(spriteSheetInfo, "Spritesheet", spriteSheetInfo.getName()); + break; + } + case 1: { + FileFilter filter = new FileNameExtensionFilter(format.toString() + " - Image", format.toString()); + chooser.setFileFilter(filter); + chooser.addChoosableFileFilter(filter); + chooser.setSelectedFile(new File(spriteSheetInfo.getName() + format.toFileExtension())); + + int result = chooser.showSaveDialog(Game.window().getRenderComponent()); + if (result == JFileChooser.APPROVE_OPTION) { + ImageSerializer.saveImage(chooser.getSelectedFile().toString(), sprite.getImage(), format); + log.log(Level.INFO, "exported spritesheet {0} to {1}", new Object[]{spriteSheetInfo.getName(), chooser.getSelectedFile()}); + } + break; + } + case 2: { + FileFilter filter = new FileNameExtensionFilter(".json" + " - " + "Spritesheet" + " JSON", "json"); + chooser.setFileFilter(filter); + chooser.addChoosableFileFilter(filter); + chooser.setSelectedFile(new File(spriteSheetInfo.getName() + "." + "json")); + + int result = chooser.showSaveDialog(Game.window().getRenderComponent()); + if (result == JFileChooser.APPROVE_OPTION) { + String fileNameWithExtension = chooser.getSelectedFile().toString(); + if (!fileNameWithExtension.endsWith(".json")) { + fileNameWithExtension += ".json"; + } + String json = AsepriteHandler.exportAnimation(spriteSheetInfo); + try (Writer writer = new FileWriter(fileNameWithExtension)) { + writer.write(json); + log.log(Level.INFO, "Exported {0} {1} to {2}", new Object[]{"Spritesheet", spriteSheetInfo.getName(), fileNameWithExtension}); + } catch (IOException e) { + e.printStackTrace(); + } + } + break; } } - } catch (IOException e) { + } catch (AsepriteHandler.ExportAnimationException | IOException e) { log.log(Level.SEVERE, e.getMessage(), e); } } diff --git a/utiliti/src/de/gurkenlabs/utiliti/swing/menus/ResourcesMenu.java b/utiliti/src/de/gurkenlabs/utiliti/swing/menus/ResourcesMenu.java index 3107eb7fe..adfa1173a 100644 --- a/utiliti/src/de/gurkenlabs/utiliti/swing/menus/ResourcesMenu.java +++ b/utiliti/src/de/gurkenlabs/utiliti/swing/menus/ResourcesMenu.java @@ -54,6 +54,10 @@ public ResourcesMenu() { exportSpriteSheets.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK)); exportSpriteSheets.addActionListener(a -> Editor.instance().exportSpriteSheets()); + JMenuItem importAnimation = new JMenuItem(Resources.strings().get("menu_assets_importAnimation")); + importAnimation.addActionListener(a -> Editor.instance().importAnimation()); + importAnimation.setEnabled(false); + Editor.instance().onLoaded(() -> { importSpriteFile.setEnabled(Editor.instance().getCurrentResourceFile() != null); importSprite.setEnabled(Editor.instance().getCurrentResourceFile() != null); @@ -63,6 +67,7 @@ public ResourcesMenu() { importTilesets.setEnabled(Editor.instance().getCurrentResourceFile() != null); importSounds.setEnabled(Editor.instance().getCurrentResourceFile() != null); exportSpriteSheets.setEnabled(Editor.instance().getCurrentResourceFile() != null); + importAnimation.setEnabled(Editor.instance().getCurrentResourceFile() != null); }); this.add(importSprite); @@ -72,6 +77,7 @@ public ResourcesMenu() { this.add(importBlueprints); this.add(importTilesets); this.add(importSounds); + this.add(importAnimation); this.addSeparator(); this.add(exportSpriteSheets); this.add(compress);