BareRTC/pkg/images.go

123 lines
3.5 KiB
Go

package barertc
import (
"bytes"
"image"
"image/jpeg"
"image/png"
"io"
"git.kirsle.net/apps/barertc/pkg/config"
"git.kirsle.net/apps/barertc/pkg/log"
"github.com/edwvee/exiffix"
"golang.org/x/image/draw"
)
var (
// TODO: configurable
MaxPhotoWidth = 1280
)
// ProcessImage treats user uploaded images:
//
// - Scales them down to a reasonable size
// - Strips EXIF metadata
//
// and returns the modified image again as bytes.
//
// Also returns the suggested preview width, height to draw the image
// at. This may be smaller than its true width x height.
//
// Filetype should be image/jpeg, image/gif or image/png.
func ProcessImage(fileType string, data []byte) ([]byte, int, int) {
reader := bytes.NewReader(data)
// Strip EXIF data.
origImage, _, err := exiffix.Decode(reader)
if err != nil {
log.Error("ProcessImage: exiffix: %s", err)
return data, config.Current.PreviewImageWidth, config.Current.PreviewImageWidth
}
reader.Seek(0, io.SeekStart)
var width, height = origImage.Bounds().Max.X, origImage.Bounds().Max.Y
log.Info("ProcessImage: taking a %dx%d image", width, height)
// Compute what size we should scale the width/height to,
// and the even smaller preview size for front-end.
var (
previewWidth = config.Current.PreviewImageWidth
previewHeight = previewWidth
)
if width >= height {
log.Debug("Its width(%d) is >= its height (%d)", width, height)
if width > config.Current.MaxImageWidth {
newWidth := config.Current.MaxImageWidth
log.Debug("\tnewWidth=%d", newWidth)
log.Debug("\tnewHeight=(%d / %d) * %d", width, height, newWidth)
height = int((float64(height) / float64(width)) * float64(newWidth))
width = newWidth
log.Debug("Its longest is width, scale to %dx%d", width, height)
}
// Compute the preview width.
if width > config.Current.PreviewImageWidth {
newWidth := config.Current.PreviewImageWidth
previewHeight = int((float64(height) / float64(width)) * float64(newWidth))
previewWidth = newWidth
}
} else {
if height > config.Current.MaxImageWidth {
newHeight := config.Current.MaxImageWidth
width = int((float64(width) / float64(height)) * float64(newHeight))
height = newHeight
log.Debug("Its longest is height, scale to %dx%d", width, height)
}
// Compute the preview height.
if height > config.Current.PreviewImageWidth {
newHeight := config.Current.PreviewImageWidth
previewWidth = int((float64(width) / float64(height)) * float64(newHeight))
previewHeight = newHeight
log.Debug("Its longest is height, scale to %dx%d", previewWidth, previewHeight)
}
}
// Scale the image.
scaledImg := Scale(origImage, image.Rect(0, 0, width, height), draw.ApproxBiLinear)
// Return the new bytes.
var buf = bytes.NewBuffer([]byte{})
switch fileType {
case "image/jpeg":
jpeg.Encode(buf, scaledImg, &jpeg.Options{
Quality: 90,
})
case "image/gif":
// Return the original data - we will only break it.
return data, width, height
case "image/png":
png.Encode(buf, scaledImg)
default:
return data, config.Current.PreviewImageWidth, config.Current.PreviewImageWidth
}
return buf.Bytes(), previewWidth, previewHeight
}
// Scale down an image. Example:
//
// scaled := Scale(src, image.Rect(0, 0, 200, 200), draw.ApproxBiLinear)
func Scale(src image.Image, rect image.Rectangle, scale draw.Scaler) image.Image {
dst := image.NewRGBA(rect)
copyRect := image.Rect(
rect.Min.X,
rect.Min.Y,
rect.Min.X+rect.Max.X,
rect.Min.Y+rect.Max.Y,
)
scale.Scale(dst, copyRect, src, src.Bounds(), draw.Over, nil)
return dst
}