From a1f90962f654abe69bcd2146d8f22ef2993c08ae Mon Sep 17 00:00:00 2001 From: Nefrace Date: Sat, 23 Mar 2024 17:22:35 +0300 Subject: [PATCH] Split code, extend GUI mode, improve error handling --- cart.odin | 60 ++++++++++++++++ cmdmode.odin | 65 +++++++++++++++++ guimode.odin | 163 ++++++++++++++++++++++++++++++++++++++++++ main.odin | 196 +-------------------------------------------------- 4 files changed, 289 insertions(+), 195 deletions(-) create mode 100644 cart.odin create mode 100644 cmdmode.odin create mode 100644 guimode.odin diff --git a/cart.odin b/cart.odin new file mode 100644 index 0000000..2585d65 --- /dev/null +++ b/cart.odin @@ -0,0 +1,60 @@ +package main + +import rl "vendor:raylib" +import "core:strings" + +CART_WIDTH :: 160 +CART_HEIGHT :: 205 + +Cart :: struct { + image : rl.Image, + texture : rl.Texture, + loaded : bool, +} + +CartLoadError :: enum { + None, + Wrong_Size, + Allocation_Error, + Wrong_Type +} + +loadCart :: proc(target: ^Cart, filename: cstring) -> CartLoadError { + if !strings.has_suffix(string(filename), ".png") { + return .Wrong_Type + } + unloadCart(target) + target.image = rl.LoadImage(filename) + if target.image.width != CART_WIDTH || target.image.height != CART_HEIGHT { + return CartLoadError.Wrong_Size + } + target.texture = rl.LoadTextureFromImage(target.image) + target.loaded = true + return CartLoadError.None +} + +unloadCart :: proc(target: ^Cart) { + if target.loaded { + rl.UnloadTexture(target.texture) + rl.MemFree(target.image.data) + } + target.loaded = false +} + +convertCart :: proc(cart: rl.Image, target: rl.Image) -> rl.Image { + newCartImage := rl.ImageCopy(target) + for x : i32 = 0; x < newCartImage.width; x += 1 { + for y : i32 = 0; y < newCartImage.height; y += 1{ + targetCol := rl.GetImageColor(target, x, y) + sourceCol := rl.GetImageColor(cart, x, y) + newCol := rl.Color{ + targetCol.r & 0b11111100 | sourceCol.r & 0b00000011, + targetCol.g & 0b11111100 | sourceCol.g & 0b00000011, + targetCol.b & 0b11111100 | sourceCol.b & 0b00000011, + targetCol.a & 0b11111100 | sourceCol.a & 0b00000011, + } + rl.ImageDrawPixel(&newCartImage, x, y, newCol) + } + } + return newCartImage +} diff --git a/cmdmode.odin b/cmdmode.odin new file mode 100644 index 0000000..d8b7f77 --- /dev/null +++ b/cmdmode.odin @@ -0,0 +1,65 @@ +package main + +import rl "vendor:raylib" +import "core:fmt" +import "core:strings" +import "core:os" + + +CmdMode :: proc(args: []string) { + if len(args) < 2 || len(args) > 3 { + fmt.eprintln( + " PicoRepico cmdline usage:", + "repico ", + "Use only 160x205 png images with PICO-8 palette", + sep = "\n", + ) + return + } + outToStd := false + if len(args) == 2 { + outToStd = true + } + + argnames := []string { + "Input file", + "Shell file", + } + images := [2]rl.Image{} + for i := 0; i < 2; i+=1 { + if !strings.has_suffix(args[i], ".png") { + fmt.eprintf("ERROR: %v is not a .png file\n", argnames[i]) + os.exit(auto_cast os.EINVAL) + } + + images[i] = rl.LoadImage(strings.unsafe_string_to_cstring(args[i])) + if !rl.IsImageReady(images[i]) { + fmt.eprintf("ERROR: can't load %v\n", argnames[i]) + os.exit(auto_cast os.EIO) + } + + if images[i].width != CART_WIDTH || images[i].height != CART_HEIGHT { + fmt.eprintf("ERROR: %v has wrong dimensions. 160x205 is expected\n", argnames[i]) + rl.UnloadImage(images[i]) + os.exit(auto_cast os.EINVAL) + } + } + defer { + for img in images { + rl.UnloadImage(img) + } + } + + newCart := convertCart(images[0], images[1]) + defer rl.UnloadImage(newCart) + + if !outToStd { + rl.ExportImage(newCart, strings.unsafe_string_to_cstring(args[2])) + } else { + filesize : i32 = 0 + ptr := rl.ExportImageToMemory(newCart, ".png", &filesize) + defer rl.MemFree(ptr) + os.write_ptr(os.stdout, ptr, auto_cast filesize) + } + fmt.eprintln("done!") +} diff --git a/guimode.odin b/guimode.odin new file mode 100644 index 0000000..a848b7a --- /dev/null +++ b/guimode.odin @@ -0,0 +1,163 @@ +package main + + +import rl "vendor:raylib" +import "core:fmt" +import "core:strings" +import "core:slice" +import "core:os" + + +W :: 400 +H :: 300 + +width : int = W +height : int = H +zoom : int = 1 + +WrongSizeErrorTimer : i32 = 0 +IsConverted : bool = false + +FILENAME_MAX :: 255 +fileNameBuffer : [FILENAME_MAX]byte +fileNameString := cstring(&fileNameBuffer[0]) +editMode := false + + +State :: enum { + CartsInput, + Export, + GotError, + Exported +} + +GraphicsMode :: proc() { + cart := Cart{} + target := Cart{} + + errorMessages := map[CartLoadError]cstring { + .None = "", + .Wrong_Size = "The image has wrong dimensions!\n160x205 PNG file expected", + .Wrong_Type = "The image is not a PNG file!\n160x205 PNG file expected", + .Allocation_Error = "Allocation error!", + } + defer delete(errorMessages) + currentError := CartLoadError.None + + rl.SetConfigFlags({rl.ConfigFlag.WINDOW_RESIZABLE}) + rl.InitWindow(W, H, "PicoRepico") + rl.SetTargetFPS(60) + + //fontData := #load("font.ttf") + //font := rl.LoadFontFromMemory(".ttf", raw_data(fontData), auto_cast len(fontData), 8, nil, 0) + font := rl.LoadFontEx("font.ttf", 32, nil, 0) + rl.GuiSetFont(font) + rl.GuiSetStyle(auto_cast rl.GuiControl.DEFAULT, auto_cast rl.GuiControlProperty.BORDER_COLOR_NORMAL, i32(rl.ColorToInt(rl.Color{211, 2, 255, 255}))) + + hw : f32 = auto_cast width / 2 + hh : f32 = auto_cast height / 2 + state := State.CartsInput + + for !rl.WindowShouldClose() { + if WrongSizeErrorTimer > 0 { + WrongSizeErrorTimer -= 1 + } + width = auto_cast rl.GetScreenWidth() + height = auto_cast rl.GetScreenHeight() + hw = auto_cast width / 2 + hh = auto_cast height / 2 + + #partial switch state { + case .CartsInput: { + mouse := rl.GetMousePosition() + if rl.IsFileDropped() { + droppedFiles := rl.LoadDroppedFiles() + defer rl.UnloadDroppedFiles(droppedFiles) + + if droppedFiles.count == 1 { + f := droppedFiles.paths[0] + if mouse.x <= hw { + fmt.eprintln("Got cart file: ", f) + currentError = loadCart(&cart, f) + if currentError != .None { + state = .GotError + } + } + if mouse.x > hw { + fmt.eprintln("Got target file: ", f) + currentError = loadCart(&target, f) + if currentError != .None { + state = .GotError + } + } + } + } + + buttonCartRect := rl.Rectangle{ + x = hw - CART_WIDTH - 20, + y = 8, + width = CART_WIDTH, + height = CART_HEIGHT, + } + buttonTargetRect := rl.Rectangle{ + x = hw + 20, + y = 8, + width = CART_WIDTH, + height = CART_HEIGHT, + } + convertButtonRect := rl.Rectangle{ + x = hw - 40, + y = auto_cast height - 38, + width = 80, + height = 30, + } + + rl.BeginDrawing() + rl.GuiDummyRec(rl.Rectangle{0, 0, auto_cast width, auto_cast height}, "") + rl.GuiButton(buttonCartRect, "Place here cart") + rl.GuiButton(buttonTargetRect, "Place here target") + + if cart.loaded { + rl.DrawTexture(cart.texture, auto_cast buttonCartRect.x, auto_cast buttonCartRect.y, rl.WHITE) + } + if target.loaded { + rl.DrawTexture(target.texture, auto_cast buttonTargetRect.x, auto_cast buttonTargetRect.y, rl.WHITE) + } + if !(cart.loaded && target.loaded) { rl.GuiDisable() } + if rl.GuiButton(convertButtonRect, "CONVERT!") { + state = .Export + } + rl.GuiEnable() + } + case .Export: { + res := rl.GuiTextInputBox(rl.Rectangle{hw - 100, 60, 200, 100}, "Input filename for result", "", "ok;cancel", fileNameString, FILENAME_MAX, nil) + if res == 0 || res == 2 { + state = .CartsInput + slice.fill(fileNameBuffer[:], 0) + } + if res == 1 { + newCart := convertCart(cart.image, target.image) + rl.ExportImage(newCart, fileNameString) + state = .Exported + } + } + case .Exported: { + if rl.GuiMessageBox(rl.Rectangle{hw-110, 80, 220, 120}, "Success!", "Cart was exported successfully", "CLOSE") != -1 { + state = .CartsInput + } + } + case .GotError: { + if rl.GuiMessageBox(rl.Rectangle{hw-150, 30, 300, 200}, "ERROR", errorMessages[currentError], "CLOSE") != -1 { + state = .CartsInput + } + } + } + + + rl.EndDrawing() + } + + unloadCart(&cart) + unloadCart(&target) +} + diff --git a/main.odin b/main.odin index 03c92cd..99190d9 100644 --- a/main.odin +++ b/main.odin @@ -1,30 +1,11 @@ package main -import "core:fmt" import rl "vendor:raylib" +import "core:fmt" import "core:mem" import "core:os" -import "core:strings" -W :: 400 -H :: 300 -CW :: 160 -CH :: 205 - -width : int = W -height : int = H -zoom : int = 1 - -Cart :: struct { - image : rl.Image, - texture : rl.Texture, - loaded : bool, -} - -WrongSizeErrorTimer : i32 = 0 -IsConverted : bool = false - main :: proc() { rl.SetTraceLogLevel(.FATAL) when ODIN_DEBUG { @@ -53,180 +34,5 @@ main :: proc() { GraphicsMode() return } - if len(args) != 3 { - fmt.eprintln( - " PicoRepico cmdline usage:", - "repico ", - "Use only 160x205 png images with PICO-8 palette", - sep = "\n", - ) - return - } CmdMode(args) } - -CartLoadError :: enum { - None, - Wrong_Size, - Allocation_Error, -} - -loadCart :: proc(target: ^Cart, filename: cstring) -> CartLoadError { - unloadCart(target) - target.image = rl.LoadImage(filename) - if target.image.width != CW || target.image.height != CH { - return CartLoadError.Wrong_Size - } - target.texture = rl.LoadTextureFromImage(target.image) - target.loaded = true - return CartLoadError.None -} - -unloadCart :: proc(target: ^Cart) { - if target.loaded { - rl.UnloadTexture(target.texture) - rl.MemFree(target.image.data) - } - target.loaded = false -} - -convertCart :: proc(cart: rl.Image, target: rl.Image) -> rl.Image { - newCartImage := rl.ImageCopy(target) - for x : i32 = 0; x < newCartImage.width; x += 1 { - for y : i32 = 0; y < newCartImage.height; y += 1{ - targetCol := rl.GetImageColor(target, x, y) - sourceCol := rl.GetImageColor(cart, x, y) - newCol := rl.Color{ - targetCol.r & 0b11111100 | sourceCol.r & 0b00000011, - targetCol.g & 0b11111100 | sourceCol.g & 0b00000011, - targetCol.b & 0b11111100 | sourceCol.b & 0b00000011, - targetCol.a & 0b11111100 | sourceCol.a & 0b00000011, - } - rl.ImageDrawPixel(&newCartImage, x, y, newCol) - } - } - return newCartImage -} - - -GraphicsMode :: proc() { - cart := Cart{} - target := Cart{} - - rl.InitWindow(W, H, "PicoRepico") - rl.SetTargetFPS(60) - - hw : f32 = auto_cast width / 2 - hh : f32 = auto_cast height / 2 - buttonCartRect := rl.Rectangle{ - x = hw - CW - 20, - y = 8, - width = CW, - height = CH, - } - buttonTargetRect := rl.Rectangle{ - x = hw + 20, - y = 8, - width = CW, - height = CH, - } - convertButtonRect := rl.Rectangle{ - x = hw - 40, - y = auto_cast height - 38, - width = 80, - height = 30, - } - - for !rl.WindowShouldClose() { - if WrongSizeErrorTimer > 0 { - WrongSizeErrorTimer -= 1 - } - - mouse := rl.GetMousePosition() - if rl.IsFileDropped() { - droppedFiles := rl.LoadDroppedFiles() - defer rl.UnloadDroppedFiles(droppedFiles) - - if droppedFiles.count == 1 { - f := droppedFiles.paths[0] - if mouse.x <= hw { - fmt.eprintln("Got cart file: ", f) - if loadCart(&cart, f) == .Wrong_Size { - WrongSizeErrorTimer = 180 - } - } - if mouse.x > hw { - fmt.eprintln("Got target file: ", f) - if loadCart(&target, f) == .Wrong_Size { - WrongSizeErrorTimer = 180 - } - } - } - } - - rl.BeginDrawing() - rl.GuiDummyRec(rl.Rectangle{0, 0, auto_cast width, auto_cast height}, "") - - rl.GuiButton(buttonCartRect, "Place here cart") - rl.GuiButton(buttonTargetRect, "Place here target") - - if WrongSizeErrorTimer > 0 { - rl.GuiLabel(rl.Rectangle{8, auto_cast height - 38, 100, 30}, "WRONG IMAGE SIZE") - } - - if cart.loaded { - rl.DrawTexture(cart.texture, auto_cast buttonCartRect.x, auto_cast buttonCartRect.y, rl.WHITE) - } - if target.loaded { - rl.DrawTexture(target.texture, auto_cast buttonTargetRect.x, auto_cast buttonTargetRect.y, rl.WHITE) - } - - if rl.GuiButton(convertButtonRect, "CONVERT!") { - newCart := convertCart(cart.image, target.image) - rl.ExportImage(newCart, "output.p8.png") - } - rl.EndDrawing() - } - - unloadCart(&cart) - unloadCart(&target) -} - - -CmdMode :: proc(args: []string) { - argnames := []string { - "input file", - "shell file", - } - images := [2]rl.Image{} - for i := 0; i < 2; i+=1 { - if !strings.has_suffix(args[i], ".png") { - fmt.eprintf("%v is not a .png file", argnames[i]) - return - } - - images[i] = rl.LoadImage(strings.unsafe_string_to_cstring(args[i])) - if !rl.IsImageReady(images[i]) { - fmt.eprintf("can't load %v", argnames[i]) - return - } - } - defer { - for img in images { - rl.UnloadImage(img) - } - } - - newCart := convertCart(images[0], images[1]) - defer rl.UnloadImage(newCart) - - if args[2] != "--" { - rl.ExportImage(newCart, strings.unsafe_string_to_cstring(args[2])) - } else { - filesize : i32 = 0 - ptr := rl.ExportImageToMemory(newCart, ".png", &filesize) - defer rl.MemFree(ptr) - os.write_ptr(os.stdout, ptr, auto_cast filesize) - } - fmt.eprintln("done!") -}