Resizing photos, db vacuuming.

* Incoming feature photos are resized and compressed to JPEG to the given dimensions and quality.
* Database is vacuumed during cleanup.
This commit is contained in:
zegkljan
2022-09-11 19:40:52 +02:00
parent 7b1c3cfc28
commit a5d98c2eb7
6 changed files with 106 additions and 6 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"math"
"net/http"
"os"
@@ -257,7 +257,7 @@ func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
internalError(gc, fmt.Errorf("failed to open 'data' 'file': %w", err))
return
}
dataBytes, err := ioutil.ReadAll(df)
dataBytes, err := io.ReadAll(df)
if err != nil {
internalError(gc, fmt.Errorf("failed to open 'data' 'file': %w", err))
return
+27 -4
View File
@@ -2,6 +2,7 @@ package server
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"fmt"
@@ -48,6 +49,9 @@ type ServerConfig struct {
ReinitDB bool
MinZoom int
DefaultCenter models.Coords
MaxPhotoX int
MaxPhotoY int
PhotoQuality int
}
func New(config ServerConfig) *Server {
@@ -62,6 +66,8 @@ func (s *Server) Run(ctx context.Context) {
s.ctx = ctx
s.setupDB()
defer s.cleanupDb()
s.setupTiles()
router := s.setupRouter()
@@ -86,6 +92,9 @@ func (s *Server) Run(ctx context.Context) {
if err := server.Shutdown(ctx); err != nil {
s.log.WithError(err).Fatal("Server forced to shutdown.")
}
}
func (s *Server) cleanupDb() {
close(s.checkpointNotice)
s.log.Info("Closing db connection pool...")
s.dbpool.Close()
@@ -96,6 +105,10 @@ func (s *Server) Run(ctx context.Context) {
s.log.WithError(err).Error("Failed to open connection for final checkpoint.")
return
}
err = sqlitex.Exec(conn, "vacuum", nil)
if err != nil {
s.log.WithError(err).Error("Failed to vacuum db.")
}
s.checkpointDb(conn, true)
conn.Close()
}
@@ -710,11 +723,22 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
return fmt.Errorf("failed to clear bindings of prepared statement: %w", err)
}
resizedPhoto, err := func() ([]byte, error) {
defer photo.File.Close()
resized, err := resizePhoto(s.config.MaxPhotoX, s.config.MaxPhotoY, s.config.PhotoQuality, photo.File)
if err != nil {
return nil, err
}
return resized, nil
}()
stmt.BindInt64(1, int64(featureID))
stmt.BindText(2, thumbnail.ContentType)
stmt.BindText(3, photo.ContentType)
stmt.BindText(3, "image/jpeg")
stmt.BindZeroBlob(4, thumbnail.Size)
stmt.BindZeroBlob(5, photo.Size)
stmt.BindZeroBlob(5, int64(len(resizedPhoto)))
_, err = stmt.Step()
if err != nil {
@@ -742,9 +766,8 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
}
err = func() error {
defer blob.Close()
defer photo.File.Close()
_, err := io.Copy(blob, photo.File)
_, err = io.Copy(blob, bytes.NewBuffer(resizedPhoto))
return err
}()
if err != nil {
+63
View File
@@ -1,6 +1,15 @@
package server
import (
"bytes"
"fmt"
"image"
"image/jpeg"
"io"
"math"
"golang.org/x/image/draw"
"cernobor.cz/oko-server/models"
)
@@ -32,3 +41,57 @@ func isUniqueFeatureID(features []models.Feature) bool {
}
return true
}
func resizePhoto(maxX, maxY, quality int, data io.Reader) ([]byte, error) {
src, _, err := image.Decode(data)
if err != nil {
return nil, fmt.Errorf("failed to decode image: %w", err)
}
if maxX == 0 && maxY == 0 {
output := &bytes.Buffer{}
jpeg.Encode(output, src, &jpeg.Options{
Quality: 90,
})
return output.Bytes(), nil
}
var dst draw.Image
srcX := src.Bounds().Max.X
srcY := src.Bounds().Max.Y
srcRatio := float64(srcX) / float64(srcY)
if maxX == 0 && srcY > maxY {
newX := int(math.Round(float64(maxY) * srcRatio))
newY := maxY
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
} else if maxY == 0 && srcX > maxX {
newX := maxX
newY := int(math.Round(float64(maxX) / srcRatio))
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
} else if srcX > maxX || srcY > maxY {
tgtRatio := float64(maxX) / float64(maxY)
var newX, newY int
if srcRatio > tgtRatio {
newX = maxX
newY = int(math.Round(float64(maxX) / srcRatio))
} else {
newX = int(math.Round(float64(maxY) * srcRatio))
newY = maxY
}
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
} else {
dst = image.NewRGBA(image.Rect(0, 0, srcX, srcY))
draw.Copy(dst, image.Point{X: 0, Y: 0}, src, src.Bounds(), draw.Over, nil)
}
output := &bytes.Buffer{}
jpeg.Encode(output, dst, &jpeg.Options{
Quality: quality,
})
return output.Bytes(), nil
}