Some bugfixes around profile picture cropping

This commit is contained in:
Noah Petherbridge 2023-07-22 11:51:58 -07:00
parent 12dcc10ff3
commit 356f94698f
3 changed files with 52 additions and 15 deletions

View File

@ -143,7 +143,7 @@ func Upload() http.HandlerFunc {
} }
// Are we uploading a profile pic? If so, set the user's pic now. // Are we uploading a profile pic? If so, set the user's pic now.
if vars["Intent"] == "profile_pic" { if vars["Intent"] == "profile_pic" && cropFilename != "" {
log.Info("User %s is setting their profile picture", user.Username) log.Info("User %s is setting their profile picture", user.Username)
user.ProfilePhoto = *p user.ProfilePhoto = *p
user.Save() user.Save()

View File

@ -40,12 +40,9 @@ func UploadPhoto(cfg UploadConfig) (string, string, error) {
dbExtension = extension dbExtension = extension
) )
switch extension { switch extension {
case ".jpg": case ".jpg", ".jpe", ".jpeg":
fallthrough
case ".jpe":
fallthrough
case ".jpeg":
extension = ".jpg" extension = ".jpg"
dbExtension = ".jpg"
case ".png": case ".png":
extension = ".png" extension = ".png"
dbExtension = ".jpg" dbExtension = ".jpg"
@ -114,12 +111,18 @@ func UploadPhoto(cfg UploadConfig) (string, string, error) {
w = cfg.Crop[2] w = cfg.Crop[2]
h = cfg.Crop[3] h = cfg.Crop[3]
) )
croppedImg := Crop(origImage, x, y, w, h) croppedImg, err := Crop(origImage, x, y, w, h)
if err != nil {
// Error during the crop: return it and just the original image filename
log.Error("Couldn't crop new profile photo: %s", err)
return filename, "", nil
}
// Write that to disk, too. // Write that to disk, too.
log.Debug("Writing cropped image to disk: %s", cropFilename) log.Debug("Writing cropped image to disk: %s", cropFilename)
if err := ToDisk(cropFilename, extension, croppedImg, &cfg); err != nil { if err := ToDisk(cropFilename, extension, croppedImg, &cfg); err != nil {
return filename, "", err log.Error("Couldn't write cropped photo to disk: %s", err)
return filename, "", nil
} }
// Return both filenames! // Return both filenames!
@ -148,11 +151,35 @@ func Scale(src image.Image, rect image.Rectangle, scale draw.Scaler) image.Image
// Crop an image, returning the new image. Example: // Crop an image, returning the new image. Example:
// //
// cropped := Crop() // cropped := Crop()
func Crop(src image.Image, x, y, w, h int) image.Image { func Crop(src image.Image, x, y, w, h int) (image.Image, error) {
// Sanity check the crop constraints, e.g. sometimes the front-end might send "203 -1 738 738" with a negative x/y value
if x < 0 {
log.Debug("Crop(%d, %d, %d, %d): x value %d too low, cap to zero", x, y, w, h, x)
x = 0
}
if y < 0 {
log.Debug("Crop(%d, %d, %d, %d): y value %d too low, cap to zero", x, y, w, h, y)
y = 0
}
if x+w > src.Bounds().Dx() {
log.Debug("Crop(%d, %d, %d, %d): width is too wide", x, y, w, h)
w = src.Bounds().Dx() - x
}
if y+h > src.Bounds().Dy() {
log.Debug("Crop(%d, %d, %d, %d): height is too tall", x, y, w, h)
h = src.Bounds().Dy() - y
}
// If they are trying to crop a 0x0 image, return an error.
if w == 0 || h == 0 {
return nil, errors.New("can't crop to a 0x0 resolution image")
}
log.Debug("Crop(): running draw.Copy with dimensions %d, %d, %d, %d", x, y, w, h)
dst := image.NewRGBA(image.Rect(0, 0, w, h)) dst := image.NewRGBA(image.Rect(0, 0, w, h))
srcrect := image.Rect(x, y, x+w, y+h) srcrect := image.Rect(x, y, x+w, y+h)
draw.Copy(dst, image.ZP, src, srcrect, draw.Over, nil) draw.Copy(dst, image.Point{}, src, srcrect, draw.Over, nil)
return dst return dst, nil
} }
// ReCrop an image, loading the original image from disk. Returns the newly created filename. // ReCrop an image, loading the original image from disk. Returns the newly created filename.
@ -170,7 +197,8 @@ func ReCrop(filename string, x, y, w, h int) (string, error) {
// Decode the image. // Decode the image.
var img image.Image var img image.Image
switch ext { switch ext {
case ".jpg": case ".jpg", ".jpeg", ".jpe":
// NOTE: new uploads enforce .jpg extension, some legacy pics may have slipped thru
img, err = jpeg.Decode(fh) img, err = jpeg.Decode(fh)
if err != nil { if err != nil {
return "", err return "", err
@ -185,7 +213,10 @@ func ReCrop(filename string, x, y, w, h int) (string, error) {
} }
// Crop it. // Crop it.
croppedImg := Crop(img, x, y, w, h) croppedImg, err := Crop(img, x, y, w, h)
if err != nil {
return "", err
}
// Write it. // Write it.
err = ToDisk(cropFilename, ext, croppedImg, nil) err = ToDisk(cropFilename, ext, croppedImg, nil)
@ -209,6 +240,11 @@ func ParseCropCoords(coords string) []int {
} }
} }
} }
// If either the width or height would be zero, the coords aren't valid.
if coords[2] == 0 || coords[3] == 0 {
return nil
}
return crop return crop
} }
@ -237,7 +273,8 @@ func ToDisk(filename string, extension string, img image.Image, cfg *UploadConfi
defer fh.Close() defer fh.Close()
switch extension { switch extension {
case ".jpg", ".png": case ".jpg", ".jpe", ".jpeg", ".png":
// NOTE: new uploads enforce .jpg extension always, some legacy pics (.png too) may have slipped thru
jpeg.Encode(fh, img, &jpeg.Options{ jpeg.Encode(fh, img, &jpeg.Options{
Quality: config.JpegQuality, Quality: config.JpegQuality,
}) })

View File

@ -45,7 +45,7 @@
{{if or (not .LoggedIn) .IsPrivate}} {{if or (not .LoggedIn) .IsPrivate}}
<h2 class="subtitle">is on {{PrettyTitle}}, a social network for nudists &amp; exhibitionists.</h2> <h2 class="subtitle">is on {{PrettyTitle}}, a social network for nudists &amp; exhibitionists.</h2>
<p> <p>
{{PrettyTitle}} is a new social network for <strong>real</strong> nudists and exhibionists. {{PrettyTitle}} is a new social network for <strong>real</strong> nudists and exhibitionists.
Join <strong>{{.User.Username}}</strong> and the others on this site by Join <strong>{{.User.Username}}</strong> and the others on this site by
<a href="/signup"><ins>creating an account</ins></a> and sending them a friend request! Please <a href="/signup"><ins>creating an account</ins></a> and sending them a friend request! Please
see <a href="/"><ins>the home page</ins></a> for all the details. see <a href="/"><ins>the home page</ins></a> for all the details.