package photos import ( "bytes" "image" "github.com/disintegration/imaging" "github.com/gen2brain/webp" ) // Object roles holding the non-destructive edited version of a photo. They // mirror the base roles and are bound in addition to them. const ( RoleEdit = "edit" RoleEditThumb = "edit_thumb" RoleEditMedium = "edit_medium" RoleEditPreview = "edit_preview" ) // applyLight renders the light adjustments into a copy of the image. The // original pixels are never modified in place. func applyLight(src image.Image, p LightParams) *image.NRGBA { // Clone returns a fresh NRGBA copy, so the source stays untouched. dst := imaging.Clone(src) master := curveLUT(p.Curve.RGB) red := curveLUT(p.Curve.Red) green := curveLUT(p.Curve.Green) blue := curveLUT(p.Curve.Blue) width := dst.Rect.Dx() for y := 0; y < dst.Rect.Dy(); y++ { row := dst.Pix[y*dst.Stride : y*dst.Stride+width*4] for i := 0; i+3 < len(row); i += 4 { if row[i+3] == 0 { continue } row[i] = uint8(adjustChannel(float64(row[i]), p, &master, &red)) row[i+1] = uint8(adjustChannel(float64(row[i+1]), p, &master, &green)) row[i+2] = uint8(adjustChannel(float64(row[i+2]), p, &master, &blue)) } } return dst } // decodeAndAdjust decodes the original and applies the light adjustments to // it, so the variants and the full-size version share one decode. func decodeAndAdjust(data []byte, p EditParams) (*image.NRGBA, error) { img, err := imaging.Decode(bytes.NewReader(data), imaging.AutoOrientation(true)) if err != nil { return nil, err } return applyLight(img, p.Light), nil } // encodeEditedVariants renders the grid variants of an edited photo. They // mirror the base ones, so the grid and the viewer can switch to them // transparently. func encodeEditedVariants(edited *image.NRGBA) (thumb, medium, preview []byte, err error) { thumbImg := imaging.Fill(edited, 200, 200, imaging.Center, imaging.Lanczos) mediumImg := imaging.Fill(edited, 800, 800, imaging.Center, imaging.Lanczos) previewImg := imaging.Fit(edited, 1200, 1200, imaging.Lanczos) opts := webp.Options{Quality: 82} var buf bytes.Buffer if err := webp.Encode(&buf, thumbImg, opts); err != nil { return nil, nil, nil, err } thumb = buf.Bytes() buf.Reset() if err := webp.Encode(&buf, mediumImg, opts); err != nil { return nil, nil, nil, err } medium = buf.Bytes() buf.Reset() if err := webp.Encode(&buf, previewImg, opts); err != nil { return nil, nil, nil, err } preview = buf.Bytes() return thumb, medium, preview, nil } // encodeEditedFull renders the full-size edited version of a photo. It is // encoded separately from the grid variants because it is the slowest step. func encodeEditedFull(edited *image.NRGBA) ([]byte, error) { var buf bytes.Buffer if err := webp.Encode(&buf, edited, webp.Options{Quality: 82}); err != nil { return nil, err } return buf.Bytes(), nil }