Have got the processor plumbing working

This commit is contained in:
Leon Mika 2026-03-26 21:16:50 +11:00
parent 036b683eab
commit 599c72d465
11 changed files with 185 additions and 39 deletions

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"image"
"image/color"
"os"
"path/filepath"
@ -12,7 +13,7 @@ import (
"lmika.dev/lmika/weiro/models"
)
func (s *Service) reprocess(ctx context.Context, session models.ImageEditSession) (imageSource, error) {
func (s *Service) reprocess(ctx context.Context, session *models.ImageEditSession) (imageSource, error) {
var img imageSource
for _, p := range session.Processors {
@ -68,6 +69,10 @@ func (s *Service) processImage(ctx context.Context, srcImg image.Image, processo
defer f.Close()
return imaging.Decode(f)
case "shadow":
shadow := makeBoxShadow(srcImg, color.Black, 4, 10, 0)
composit := imaging.OverlayCenter(shadow, srcImg, 1.0)
return composit, nil
}
return nil, fmt.Errorf("unknown processor type: %v", processor.Type)
}

View file

@ -27,31 +27,15 @@ func New(
}
}
func (s *Service) LoadImageVersion(ctx context.Context, sessionID string, versionID string) (mimeType string, rw func() (io.ReadCloser, error), err error) {
func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (*models.ImageEditSession, error) {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return "", nil, err
}
session, err := s.sessionStore.get(sessionID)
if err != nil {
return "", nil, err
} else if session.SiteID != site.ID || session.UserID != user.ID {
return "", nil, models.PermissionError
}
return s.sessionStore.getImage(session, versionID+"."+session.ImageExt)
}
func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (models.ImageEditSession, error) {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return models.ImageEditSession{}, err
return nil, err
}
upload, _, err := s.uploadService.OpenUpload(ctx, baseUploadID)
if err != nil {
return models.ImageEditSession{}, err
return nil, err
}
var ext string
@ -61,7 +45,7 @@ func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (models.Im
case "image/png":
ext = "png"
default:
return models.ImageEditSession{}, models.UnsupportedImageFormat
return nil, models.UnsupportedImageFormat
}
newSession := models.ImageEditSession{
@ -74,6 +58,7 @@ func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (models.Im
UpdatedAt: time.Now().UTC(),
Processors: []models.ImageEditProcessor{
{
ID: models.NewNanoID(),
Type: "copy-upload",
Props: mustToJSON(models.CopyUploadProps{UploadID: baseUploadID}),
},
@ -81,15 +66,67 @@ func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (models.Im
}
newSession.RecalcVersionIDs()
if err := s.sessionStore.create(newSession); err != nil {
return models.ImageEditSession{}, err
if err := s.sessionStore.save(&newSession); err != nil {
return nil, err
}
if _, err := s.reprocess(ctx, newSession); err != nil {
return models.ImageEditSession{}, err
if _, err := s.reprocess(ctx, &newSession); err != nil {
return nil, err
}
return newSession, nil
return &newSession, nil
}
func (s *Service) LoadImageVersion(ctx context.Context, sessionID string, versionID string) (mimeType string, rw func() (io.ReadCloser, error), err error) {
session, err := s.loadAndVerifySession(ctx, sessionID)
if err != nil {
return "", nil, err
}
return s.sessionStore.getImage(session, versionID+"."+session.ImageExt)
}
type AddProcessorReq struct {
Type string `json:"type"`
}
func (s *Service) AddProcessor(ctx context.Context, sessionID string, req AddProcessorReq) (*models.ImageEditSession, error) {
session, err := s.loadAndVerifySession(ctx, sessionID)
if err != nil {
return nil, err
}
// TODO: verify processor, etc.
session.Processors = append(session.Processors, models.ImageEditProcessor{
ID: models.NewNanoID(),
Type: req.Type,
})
session.RecalcVersionIDs()
if err := s.sessionStore.save(session); err != nil {
return nil, err
}
if _, err := s.reprocess(ctx, session); err != nil {
return nil, err
}
return session, nil
}
func (s *Service) loadAndVerifySession(ctx context.Context, sessionID string) (*models.ImageEditSession, error) {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return nil, err
}
session, err := s.sessionStore.get(sessionID)
if err != nil {
return nil, err
} else if session.SiteID != site.ID || session.UserID != user.ID {
return nil, models.PermissionError
}
return session, nil
}
func (s *Service) fetchSiteAndUser(ctx context.Context) (models.Site, models.User, error) {

View file

@ -0,0 +1,35 @@
package imgedit
import (
"image"
"image/color"
"github.com/disintegration/imaging"
)
func makeBoxShadow(maskImg image.Image, shadowColor color.Color, sigma float64, shadowMargin, offsetY int) image.Image {
w, h := maskImg.Bounds().Dx(), maskImg.Bounds().Dy()
cr, cg, cb, _ := shadowColor.RGBA()
cr8, cg8, cb8 := uint8(cr>>8), uint8(cg>>8), uint8(cb>>8)
// New box image
backing := image.NewNRGBA(image.Rect(0, 0, w+shadowMargin*2, h+shadowMargin*2+offsetY))
newImg := image.NewNRGBA(image.Rect(0, 0, w+shadowMargin*2, h+shadowMargin*2+offsetY))
for x := 0; x < w+shadowMargin*2; x++ {
for y := 0; y < h+shadowMargin*2; y++ {
var c = color.NRGBA{R: 255, G: 255, B: 255, A: 0}
if x >= shadowMargin-4 && y >= shadowMargin-4 && x <= w+shadowMargin+4 && y <= h+shadowMargin+4 {
_, _, _, a := maskImg.At(x-shadowMargin, y-shadowMargin).RGBA()
c = color.NRGBA{R: cr8, G: cg8, B: cb8, A: uint8(a >> 8)}
}
backing.SetNRGBA(x, y, color.NRGBA{R: 255, G: 255, B: 255, A: 0})
newImg.SetNRGBA(x, y+offsetY, c)
}
}
// Blur
blurredImage := imaging.Blur(newImg, sigma)
backing = imaging.OverlayCenter(backing, blurredImage, 0.6)
return backing
}

View file

@ -13,7 +13,7 @@ type sessionStore struct {
baseDir string
}
func (ss *sessionStore) create(newSession models.ImageEditSession) error {
func (ss *sessionStore) save(newSession *models.ImageEditSession) error {
sessionMeta, err := json.Marshal(newSession)
if err != nil {
return err
@ -28,21 +28,21 @@ func (ss *sessionStore) create(newSession models.ImageEditSession) error {
return nil
}
func (ss *sessionStore) get(guid string) (models.ImageEditSession, error) {
func (ss *sessionStore) get(guid string) (*models.ImageEditSession, error) {
sessionDataBts, err := os.ReadFile(filepath.Join(ss.baseDir, guid, "session.json"))
if err != nil {
return models.ImageEditSession{}, err
return nil, err
}
sessionData := models.ImageEditSession{}
if err := json.Unmarshal(sessionDataBts, &sessionData); err != nil {
return models.ImageEditSession{}, err
return nil, err
}
return sessionData, nil
return &sessionData, nil
}
func (ss *sessionStore) getImage(session models.ImageEditSession, imageFilename string) (string, func() (io.ReadCloser, error), error) {
func (ss *sessionStore) getImage(session *models.ImageEditSession, imageFilename string) (string, func() (io.ReadCloser, error), error) {
fullPath := filepath.Join(ss.baseDir, session.GUID, imageFilename)
if s, err := os.Stat(fullPath); err != nil {
return "", nil, err