Files
amsterdam/ui/images.go
T
2026-02-23 22:41:59 -07:00

168 lines
4.5 KiB
Go

/*
* Amsterdam Web Communities System
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
package ui
import (
"bytes"
"embed"
"errors"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"mime"
"mime/multipart"
"net/http"
"path/filepath"
"strconv"
"strings"
"git.erbosoft.com/amy/amsterdam/database"
"github.com/disintegration/imaging"
"github.com/labstack/echo/v4"
)
//go:embed static_images/*
var static_images embed.FS
// Constants for default photo sizes.
const (
UserPhotoWidth = 100
UserPhotoHeight = 100
UserPhotoMaxBytes = 2097152 // 2 Mb
CommunityLogoWidth = 110
CommunityLogoHeight = 60
CommunityLogoMaxBytes = 2097152 // 2 Mb
)
/* mimeTypeFromFilenane returns the MIME type of a file, given its filename.
* Parameters:
* filaname - The name of the file to be tested.
* Returns:
* The file's inferred MIME type.
*/
func mimeTypeFromFilename(filename string) string {
return mime.TypeByExtension(filename[strings.LastIndex(filename, "."):])
}
/* AmServeImage serves an image from internal storage.
* Parameters:
* c - The Echo context for this request.
* Returns:
* Standard Go error return.
*/
func AmServeImage(c echo.Context) error {
components := strings.SplitAfter(c.Request().URL.Path, "/")
var err error = nil
if len(components) == 4 {
switch components[2] {
case "builtin/":
var b []byte
b, err = static_images.ReadFile(filepath.Join("static_images", components[3]))
if err == nil {
return c.Blob(http.StatusOK, mimeTypeFromFilename(components[3]), b)
}
case "store/":
var id int
id, err = strconv.Atoi(components[3])
if err == nil {
var img *database.ImageStore
img, err = database.AmLoadImage(c.Request().Context(), int32(id))
if err == nil {
return c.Blob(http.StatusOK, img.MimeType, img.Data)
}
}
}
}
if err == nil {
err = fmt.Errorf("image not found: %s", c.Request().URL.Path)
}
return c.String(http.StatusNotFound, err.Error())
}
/* AmServeVeniceCompatibleImage serves an image from the image store under a Venice-compatible URI.
* Parameters:
* c - The Echo context for this request.
* Returns:
* Standard Go error return.
*/
func AmServeVeniceCompatibleImage(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err == nil {
var img *database.ImageStore
img, err = database.AmLoadImage(c.Request().Context(), int32(id))
if err == nil {
return c.Blob(http.StatusOK, img.MimeType, img.Data)
}
}
if err == nil {
err = fmt.Errorf("image not found: %s", c.Request().URL.Path)
}
return c.String(http.StatusNotFound, err.Error())
}
/* AmProcessUploadedImage takes an image and resizes it to a specified size, returning its data.
* Parameters:
* fileheader - The multipart file header from the uploaded file.
* width - New image width in pizels.
* height - New image height in pixels.
* maxbytes - The maximum size of the user photo.
* Returns:
* Image data as a byte array.
* The MIME type of the image data.
* Standard Go error status.
*/
func AmProcessUploadedImage(fileheader *multipart.FileHeader, width, height, maxbytes int) ([]byte, string, error) {
// test size
if fileheader.Size > int64(maxbytes) {
return nil, "", errors.New("file is too large; please try again")
}
// open the file
file, err := fileheader.Open()
if err != nil {
return nil, "", err
}
defer file.Close()
// load the image from the file
img, format, err := image.Decode(file)
if err != nil {
return nil, "", err
}
// resize the image using high-quality Lanczos filter
resized := imaging.Resize(img, width, height, imaging.Lanczos)
// re-encode it to the original format, or JPEG if that's not possible
var buf bytes.Buffer
var outType string
switch strings.ToLower(format) {
case "jpeg", "jpg":
err = jpeg.Encode(&buf, resized, &jpeg.Options{Quality: 90})
outType = "image/jpeg"
case "gif":
err = gif.Encode(&buf, resized, &gif.Options{NumColors: 256})
outType = "image/gif"
case "png":
err = png.Encode(&buf, resized)
outType = "image/png"
default:
err = jpeg.Encode(&buf, resized, &jpeg.Options{Quality: 90})
outType = "image/jpeg"
}
if err != nil {
return nil, "", err
}
return buf.Bytes(), outType, nil
}