Skip to content

Commit

Permalink
dev: move selection to Ajax
Browse files Browse the repository at this point in the history
* add Gif display first shot
  • Loading branch information
zaggash committed Nov 28, 2023
1 parent 668c1d6 commit c9338c8
Show file tree
Hide file tree
Showing 20 changed files with 569 additions and 331 deletions.
Binary file removed PixelImages/JPEG_example_flower.jpg
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/gin-gonic/gin"
"github.com/go-errors/errors"
"github.com/zaggash/led-matrix-ui/utils"
"github.com/zaggash/led-matrix-ui/webui"
)

Expand All @@ -15,7 +16,8 @@ type httpResponse struct {
Description string
}

func (d *Display) Run(pixelFolder string) {
func (d *Display) Run() {
pixelsFolder := utils.GetPixelsFolder()
// Set the router as the default one shipped with Gin
router := gin.Default()
//gin.SetMode(gin.ReleaseMode) // TODO : Set to Production Mode !
Expand All @@ -28,13 +30,12 @@ func (d *Display) Run(pixelFolder string) {

// Set Webui
var webStatic = "/public/"
var webPixels = pixelFolder
//// Enable subdir from embedFS assets to /public
subAssets, _ := fs.Sub(webui.EmbedAssets, "assets")
router.StaticFS(webStatic, http.FS(subAssets))

//// Enable Pixel Images folder to /pixels
router.Static(webPixels, pixelFolder)
router.Static(pixelsFolder, pixelsFolder)
router.HTMLRender = webui.LoadTemplates(webui.EmbedTemplates)

// Define favicon as favicon.ico and use it in html templates
Expand All @@ -54,14 +55,13 @@ func (d *Display) Run(pixelFolder string) {
api := router.Group("/api")
{
api.GET("/ping", healthcheck)
//api.POST("/upload", UploadFile)
api.POST("/draw", drawImage(d, pixelFolder))
//api.POST("/settings", GetSettings)
api.GET("/images", listImages(pixelFolder))
api.GET("/images/:mime", listImages(pixelFolder))
api.POST("/upload", uploadFile)
api.POST("/draw/:type", d.drawImage)
api.GET("/images/:type", listImages)
}
// Start and run the server
router.Run(":3000")

}

func errorHandler(c *gin.Context, err any) {
Expand Down
128 changes: 107 additions & 21 deletions api/draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,132 @@ package api
import (
"image"
"image/draw"
"image/gif"
"log"
"net/http"
"os"
"time"

"github.com/disintegration/imaging"
"github.com/gin-gonic/gin"
"github.com/zaggash/led-matrix-ui/display"
"github.com/zaggash/led-matrix-ui/utils"
)

type Display struct {
*display.Config
*utils.Display
}

func New(c *display.Config) *Display {
func New(c *utils.Display) *Display {
return &Display{c}
}

func drawImage(d *Display, p string) gin.HandlerFunc {
return gin.HandlerFunc(func(ctx *gin.Context) {
func (d *Display) drawImage(ctx *gin.Context) {
imgPath := ctx.PostForm("path")
imgName := ctx.PostForm("name")
paramType := ctx.Param("type")

imgPath := ctx.PostForm("Path")
if d.Channel != nil {
close(d.Channel)
}
d.Toolkit.Canvas.Clear()

f, err := os.Open(imgPath)
f, err := os.Open(imgPath)
if err != nil {
log.Fatalln(err)
}
defer f.Close()

switch paramType {
case "static":
img, err := decodeImage(f)
if err != nil {
log.Fatalln(err)
log.Panicln(err)
return
}
defer f.Close()

img, _, err := image.Decode(f)
err = drawImage(d, img)
if err != nil {
log.Fatalln(err)
log.Panicln(err)
return
}
case "animated":
gif, err := decodeGif(f)
if err != nil {
log.Panicln(err)
return
}
d.Channel = drawGif(d, gif)
default:
log.Fatalln("Nothing...")
}

send := `{"showMessage": "` + imgName + ` displayed."}`
http_code := http.StatusOK
if d.Matrix.Render() != nil {
send = "Error!"
http_code = http.StatusInternalServerError
}

ctx.Header("HX-Trigger", send)
ctx.Data(http_code, "text/html", nil)
}

func decodeImage(file *os.File) (image.Image, error) {
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return img, nil
}

w, h := d.Matrix.Geometry()
d.Toolkit.Canvas.Clear() // Clear current display
img = imaging.Fill(img, w, h, imaging.Center, imaging.Lanczos)
draw.Draw(d.Toolkit.Canvas, d.Toolkit.Canvas.Bounds(), img, image.ZP, draw.Over)
d.Matrix.Render()
func decodeGif(file *os.File) (*gif.GIF, error) {
gif, err := gif.DecodeAll(file)
if err != nil {
return nil, err
}
return gif, nil
}

func drawImage(d *Display, img image.Image) error {
w, h := d.Matrix.Geometry()
img = imaging.Fill(img, w, h, imaging.Center, imaging.Lanczos)
draw.Draw(d.Toolkit.Canvas, d.Toolkit.Canvas.Bounds(), img, image.Point{}, draw.Over)
return nil
}

func drawGif(d *Display, gif *gif.GIF) chan bool {
quit := make(chan bool)

loop := 0
delays := make([]time.Duration, len(gif.Delay))
images := make([]image.Image, len(gif.Image))
for i, image := range gif.Image {
images[i] = image
delays[i] = time.Millisecond * time.Duration(gif.Delay[i]) * 10
}

go func() {
l := len(images)
i := 0
for {
select {
case <-quit:
return
default:
start := time.Now()
defer func() { time.Sleep(delays[i] - time.Since(start)) }()
drawImage(d, images[i])
}

i++
if i > l-1 {
if loop == 0 {
i = 0
continue
}
break
}
}
}()

// DEBUG: Print POST body request
// body, _ := io.ReadAll(ctx.Request.Body)
// println(string(body))
})
return quit
}
66 changes: 0 additions & 66 deletions api/images.go

This file was deleted.

101 changes: 101 additions & 0 deletions api/list_images.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package api

import (
"io/fs"
"log"
"net/http"
"path/filepath"
"strings"

"github.com/gabriel-vasile/mimetype"
"github.com/gin-gonic/gin"
"github.com/zaggash/led-matrix-ui/utils"
)

type localImage struct {
Name string
Path string
Size int64 // Size in bytes
MimeType string
}

func listImages(ctx *gin.Context) {
var err error
var images []localImage
pixelsFolder := utils.GetPixelsFolder()
requestedType := ctx.Param("type")

err = filepath.WalkDir(pixelsFolder, func(filename string, file fs.DirEntry, err error) error {
if !file.IsDir() {
fileMime, err := mimetype.DetectFile(filename)
if err != nil {
return err
}

info, err := file.Info()
if err != nil {
return err
}

if shouldIncludeImage(requestedType, fileMime.String()) {
images = append(images, localImage{
Name: info.Name(),
Path: filename,
Size: info.Size(),
MimeType: fileMime.String(),
})
}
}
return nil
})
if err != nil {
log.Println(err)
}

//ctx.JSON(http.StatusOK, images)
ctx.Data(http.StatusOK, "text/html", createGallery(requestedType, images))
}

func createGallery(requestedType string, images []localImage) []byte {
html := ""
for _, img := range images {
card :=
`<div class="col col-3 mb-3">
<div class="card h-100 text-center">
<div class="card-header text-nowrap overflow-auto">` + img.Name + `</div>
<div class="ratio ratio-1x1">
<img src="` + img.Path + `" class="card-img-top h-100 border-bottom border-1 rounded-0" alt="` + img.Name + `" />
</div>
<div class="card-body d-flex justify-content-center">
<button class="btn btn-primary" hx-post="/api/draw/` + requestedType + `" hx-trigger="click" hx-swap="none" hx-vals='{"name":"` + img.Name + `", "path":"` + img.Path + `"}' >
Apply
</button>
</div>
</div>
</div>`
html += card
}
return []byte(html)
}

// Compare images on disk to the API mimetype parameter
func shouldIncludeImage(param, mimeType string) bool {
switch param {
case "animated":
return contains(mimeType, []string{"image/gif"})
case "static":
return (contains(mimeType, []string{"image/"}) && !contains(mimeType, []string{"image/gif"}))
default:
return false
}
}

// Check if s contains at least one string value from arr
func contains(s string, arr []string) bool {
for _, a := range arr {
if strings.Contains(s, a) {
return true
}
}
return false
}
Loading

0 comments on commit c9338c8

Please sign in to comment.