From 117c3a2dab2dbf7cdca3dc591ae8c6365bb4d435 Mon Sep 17 00:00:00 2001 From: lunarcleint Date: Tue, 14 Jan 2025 17:37:21 -0700 Subject: [PATCH] image uploader --- source/funkin/backend/utils/CoolUtil.hx | 54 +++++++++++- .../character/CharacterPropertiesWindow.hx | 14 +++- .../character/CharacterSpriteScreen.hx | 65 ++++++++++++++ .../editors/charter/SongCreationScreen.hx | 6 +- source/funkin/editors/extra/DrawAxis.hx | 2 + source/funkin/editors/ui/UIFileExplorer.hx | 20 +++-- source/funkin/editors/ui/UIImageExplorer.hx | 84 +++++++++++++++++++ source/funkin/game/Character.hx | 4 + 8 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 source/funkin/editors/character/CharacterSpriteScreen.hx create mode 100644 source/funkin/editors/ui/UIImageExplorer.hx diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 715a68ef..a018ce8d 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -1,5 +1,9 @@ package funkin.backend.utils; +import flixel.graphics.FlxGraphic; +import openfl.display.BitmapData; +import flixel.graphics.frames.FlxAtlasFrames; +import flixel.graphics.frames.FlxFramesCollection; #if sys import sys.FileSystem; #end @@ -299,7 +303,7 @@ final class CoolUtil label++; rSize /= 1024; } - return Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2) + sizeLabels[label]; + return Std.int(rSize) + ((label <= 1) ? "" : "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)) + sizeLabels[label]; } /** @@ -1088,6 +1092,54 @@ final class CoolUtil } return result; } + + /** + * ! REQUIRES FULL PATH!!! + * @param path + * @return Bool + */ + public static function imageHasFrameData(path:String):String { + if (FileSystem.exists(Path.withExtension(path, "xml"))) return "xml"; + if (FileSystem.exists(Path.withExtension(path, "txt"))) return "txt"; + if (FileSystem.exists(Path.withExtension(path, "json"))) return "json"; + + return null; + } + + public static function loadFramesFromData(data:String, ext:String = null):FlxFramesCollection { + var frames:FlxFramesCollection = null; + var tempBitmap:BitmapData = new BitmapData(1, 1, false); + + // Worlds hackiest work around alert??? + var graphic:FlxGraphic = FlxG.bitmap.add(tempBitmap); + @:privateAccess { + graphic.width = 9999999; + graphic.height = 9999999; + } + + try { + switch (ext) { + case "xml": frames = FlxAtlasFrames.fromSparrow(graphic, Xml.parse(data)); + case "txt": frames = FlxAtlasFrames.fromSpriteSheetPacker(graphic, data); + case "json": frames = FlxAtlasFrames.fromAseprite(graphic, data); + } + } catch (e) {trace(e);} + + return frames; + } + + public static function getAnimsListFromFrames(frames:FlxFramesCollection, ext:String = null):Array { + if (frames == null) return null; + + var animsList:Array = []; + for (frame in frames.frames) { + var animName:String = ext == "txt" ? frame.name.split("_")[0] : frame.name.substr(0, frame.name.length-4); + if (!animsList.contains(animName)) + animsList.push(animName); + } + + return animsList; + } } /** diff --git a/source/funkin/editors/character/CharacterPropertiesWindow.hx b/source/funkin/editors/character/CharacterPropertiesWindow.hx index 6f3c40e7..380d72fe 100644 --- a/source/funkin/editors/character/CharacterPropertiesWindow.hx +++ b/source/funkin/editors/character/CharacterPropertiesWindow.hx @@ -10,6 +10,7 @@ class CharacterPropertiesWindow extends UISliceSprite { public var positionYStepper:UINumericStepper; public var scaleStepper:UINumericStepper; public var editCharacterButton:UIButton; + public var editSpriteButton:UIButton; public var flipXCheckbox:UICheckbox; public var cameraXStepper:UINumericStepper; @@ -49,9 +50,14 @@ class CharacterPropertiesWindow extends UISliceSprite { members.push(scaleStepper); addLabelOn(scaleStepper, "Scale"); - editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y, "Edit Info", editCharacterInfoUI); + editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y-20, "Edit Info", editCharacterInfoUI, 120, 24); + editCharacterButton.field.size -= 2; members.push(editCharacterButton); + editSpriteButton = new UIButton(editCharacterButton.x, editCharacterButton.y+24+6, "Edit Sprite", editCharacterSpriteUI, 120, 24); + editSpriteButton.field.size -= 2; + members.push(editSpriteButton); + flipXCheckbox = new UICheckbox(scaleStepper.x+22, scaleStepper.y+32+14, "Flipped?", character.isPlayer ? !character.__baseFlipped : character.__baseFlipped); flipXCheckbox.onChecked = (checked:Bool) -> {this.changeFlipX(checked);}; members.push(flipXCheckbox); @@ -130,6 +136,12 @@ class CharacterPropertiesWindow extends UISliceSprite { })); } + public function editCharacterSpriteUI() { + CharacterEditor.instance.openSubState(new CharacterSpriteScreen(character.sprite, (sprite:String) -> { + trace(sprite); + })); + } + public function changeFlipX(newFlipX:Bool) @:privateAccess { character.flipX = character.isPlayer ? !newFlipX : newFlipX; character.__baseFlipped = character.flipX; diff --git a/source/funkin/editors/character/CharacterSpriteScreen.hx b/source/funkin/editors/character/CharacterSpriteScreen.hx new file mode 100644 index 00000000..f6ef2d39 --- /dev/null +++ b/source/funkin/editors/character/CharacterSpriteScreen.hx @@ -0,0 +1,65 @@ +package funkin.editors.character; + +import flixel.text.FlxText.FlxTextFormat; +import flixel.text.FlxText.FlxTextFormatMarkerPair; + +class CharacterSpriteScreen extends UISubstateWindow { + private var imagePath:String = null; + private var onSave:String -> Void = null; + + public var imageExplorer:UIFileExplorer; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public function new(imagePath:String, ?onSave:String->Void) { + super(); + this.imagePath = imagePath; + if (onSave != null) this.onSave = onSave; + } + + public override function create() { + winTitle = 'Edit character sprite'; + winWidth = 360; winHeight = 183; + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + super.create(); + + imageExplorer = new UIImageExplorer(20, windowSpr.y + 30 + 16 + 20, 320, 58, (_, _) -> {refreshWindowSize();}); + add(imageExplorer); + addLabelOn(imageExplorer, "Character Image File").applyMarkup( + "Character Image File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { + close(); + }, 125); + saveButton.x -= saveButton.bWidth; + saveButton.y -= saveButton.bHeight; + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Cancel", function() { + close(); + }, 125); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + //closeButton.y -= closeButton.bHeight; + add(closeButton); + add(saveButton); + + refreshWindowSize(); + } + + public function refreshWindowSize() { + windowSpr.bWidth = 20 + imageExplorer.bWidth + 20; + windowSpr.bHeight = 30 + 16 + 20 + imageExplorer.bHeight + 14 + saveButton.bHeight + 14; + + saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - saveButton.bWidth; + closeButton.x = saveButton.x - 20 - closeButton.bWidth; + closeButton.y = saveButton.y = imageExplorer.y + imageExplorer.bHeight + 14; + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index becf6efd..79203dd8 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -82,7 +82,8 @@ class SongCreationScreen extends UISubstateWindow { var voicesUIText:UIText = null; - instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (res) { + instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (path, res) { + if (path == null || res == null) return; var audioPlayer:UIAudioPlayer = new UIAudioPlayer(instExplorer.x + 8, instExplorer.y + 8, res); instExplorer.members.push(audioPlayer); instExplorer.uiElement = audioPlayer; @@ -92,7 +93,8 @@ class SongCreationScreen extends UISubstateWindow { "Inst Audio File $* Required$", [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); - voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (res) { + voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (path, res) { + if (path == null || res == null) return; var audioPlayer:UIAudioPlayer = new UIAudioPlayer(voicesExplorer.x + 8, voicesExplorer.y + 8, res); voicesExplorer.members.push(audioPlayer); voicesExplorer.uiElement = audioPlayer; diff --git a/source/funkin/editors/extra/DrawAxis.hx b/source/funkin/editors/extra/DrawAxis.hx index 0e37c870..c3e2a0d9 100644 --- a/source/funkin/editors/extra/DrawAxis.hx +++ b/source/funkin/editors/extra/DrawAxis.hx @@ -7,6 +7,7 @@ class DrawAxis extends FlxObject { public override function draw() { super.draw(); + /* for (camera in cameras) { var gfx:Graphics = FlxG.renderBlit ? { FlxSpriteUtil.flashGfx.clear(); @@ -27,5 +28,6 @@ class DrawAxis extends FlxObject { if (FlxG.renderBlit) camera.buffer.draw(FlxSpriteUtil.flashGfxSprite); } + */ } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIFileExplorer.hx b/source/funkin/editors/ui/UIFileExplorer.hx index 1e0154c7..9a4b86ac 100644 --- a/source/funkin/editors/ui/UIFileExplorer.hx +++ b/source/funkin/editors/ui/UIFileExplorer.hx @@ -11,24 +11,26 @@ class UIFileExplorer extends UISliceSprite { public var deleteIcon:FlxSprite; public var file:Bytes = null; - public var onFile:Bytes->Void; + public var filePath:String = null; + public var onFile:(String, Bytes)->Void; - public var uiElement:UISprite; + public var uiElement:FlxSprite; - public function new(x:Float, y:Float, ?w:Int, ?h:Int, fileType:String = "txt", ?onFile:Bytes->Void) { + public function new(x:Float, y:Float, ?w:Int, ?h:Int, fileType:String = "txt", ?onFile:(String, Bytes)->Void) { super(x, y, (w != null ? w : 320), (h != null ? h : 58), 'editors/ui/inputbox'); if (onFile != null) this.onFile = onFile; uploadButton = new UIButton(x + 8, y+ 8, null, function () { var fileDialog = new FileDialog(); - fileDialog.onOpen.add(function(res) { - file = cast res; + fileDialog.onSelect.add(function(path) { + filePath = path; + file = cast sys.io.File.getBytes(path); deleteButton.visible = deleteButton.selectable = deleteIcon.visible = !(uploadButton.visible = uploadButton.selectable = false); - if (this.onFile != null) this.onFile(file); + if (this.onFile != null) this.onFile(filePath, file); }); - fileDialog.open(fileType); + fileDialog.browse(OPEN, fileType); }, bWidth - 16, bHeight - 16); members.push(uploadButton); @@ -42,7 +44,7 @@ class UIFileExplorer extends UISliceSprite { deleteIcon = new FlxSprite(deleteButton.x + ((bHeight - 16)/2) - 8, deleteButton.y + ((bHeight - 16)/2) - 8).loadGraphic(Paths.image('editors/delete-button')); deleteIcon.antialiasing = false; - members.push(deleteIcon); + deleteButton.members.push(deleteIcon); deleteButton.visible = deleteButton.selectable = deleteIcon.visible = false; } @@ -68,7 +70,7 @@ class UIFileExplorer extends UISliceSprite { uiElement.destroy(); } - file = null; + file = null; onFile(null, null); MemoryUtil.clearMajor(); deleteButton.visible = deleteButton.selectable = deleteIcon.visible = !(uploadButton.visible = uploadButton.selectable = true); diff --git a/source/funkin/editors/ui/UIImageExplorer.hx b/source/funkin/editors/ui/UIImageExplorer.hx new file mode 100644 index 00000000..d5d68414 --- /dev/null +++ b/source/funkin/editors/ui/UIImageExplorer.hx @@ -0,0 +1,84 @@ +package funkin.editors.ui; + +import flixel.text.FlxText.FlxTextFormat; +import flixel.text.FlxText.FlxTextFormatMarkerPair; +import sys.io.File; +import sys.FileSystem; +import haxe.io.Bytes; +import haxe.io.Path; +import openfl.display.BitmapData; + +using StringTools; +using funkin.backend.utils.BitmapUtil; + +class UIImageExplorer extends UIFileExplorer { + public function new(x:Float, y:Float, ?w:Int, ?h:Int, ?onFile:(String, Bytes)->Void) { + super(x, y, w, h, "png, jpg", function (filePath, file) { + if (filePath != null && file != null) uploadImage(filePath, file); + if (onFile != null) onFile(filePath, file); + }); + + deleteButton.bWidth = 26; + deleteButton.bHeight = 26; + } + + public var fileText:UIText; + + public function uploadImage(filePath:String, file:Bytes) { + var imagePath:Dynamic = Path.normalize(filePath); + + var dataPathExt:String = CoolUtil.imageHasFrameData(imagePath); + var animationList:Array = []; + + if (dataPathExt != null) + animationList = CoolUtil.getAnimsListFromFrames(CoolUtil.loadFramesFromData(File.getContent(Path.withExtension(imagePath, dataPathExt)), dataPathExt), dataPathExt); + + uiElement = new FlxSprite().loadGraphic(BitmapData.fromBytes(file).crop()); + + var imageScale:Float = 1; + if (uiElement.width < 300 || uiElement.height < 200) + imageScale = Math.max(300/uiElement.width, 200/uiElement.height); + else if (uiElement.width > 700 || uiElement.height > 500) + imageScale = Math.min(700/uiElement.width, 500/uiElement.height); + + uiElement.scale.set(imageScale, imageScale); + uiElement.updateHitbox(); + + bWidth = Std.int(uiElement.width)+32; bHeight = Std.int(uiElement.height)+32+deleteButton.bHeight+4; + uiElement.x = x+16; uiElement.y = y+16+deleteButton.bHeight+4; + + uiElement.antialiasing = true; + members.push(uiElement); + + imagePath = new Path(imagePath); + + fileText = new UIText(x+20, y+16, bWidth-20-deleteButton.bWidth-16, '${imagePath.file}.${imagePath.ext} (${CoolUtil.getSizeString(file.length)}, ${animationList.length} Animations Found)'); + if (animationList.length <= 0) fileText.applyMarkup( + '${imagePath.file}.${imagePath.ext} (${CoolUtil.getSizeString(file.length)}, #NO Animations Found#)', + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "#")]); + members.push(fileText); + + deleteButton.x = x + bWidth - deleteButton.bWidth - 16; + deleteButton.y = y + 12; + + deleteIcon.x = deleteButton.x + deleteButton.bWidth/2 - 8; + deleteIcon.y = deleteButton.y + deleteButton.bHeight/2 - 8; + + /* + var normalFilePath:String = Path.normalize(filePath); + var gamePath:String = Path.normalize(Sys.getCwd()); + + if (normalFilePath.contains(gamePath)) + normalFilePath = normalFilePath.replace(gamePath, "."); + */ + } + + public override function removeFile() { + bWidth = 320; bHeight = 58; + + members.remove(fileText); + fileText.destroy(); + + super.removeFile(); + } +} \ No newline at end of file diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index ff6ae27e..07963143 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -261,6 +261,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public function drawHitbox() { if (_matrix == null || frame == null) return; + /* for (camera in cameras) { var gfx:Graphics = FlxG.renderBlit ? { FlxSpriteUtil.flashGfx.clear(); @@ -276,9 +277,11 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (FlxG.renderBlit) camera.buffer.draw(FlxSpriteUtil.flashGfxSprite); } + */ } public function drawCamera() { + /* for (camera in cameras) { var gfx:Graphics = FlxG.renderBlit ? { FlxSpriteUtil.flashGfx.clear(); @@ -301,6 +304,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (FlxG.renderBlit) camera.buffer.draw(FlxSpriteUtil.flashGfxSprite); } + */ } public var singAnims = ["singLEFT", "singDOWN", "singUP", "singRIGHT"];