mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-10-31 02:28:31 -04:00
Hold a free thumbnail axis to the maximum thumbnail size
A box like 0x2048 passed the check, and the aspect ratio of a very wide source then sized the other side without a bound.
This commit is contained in:
1 parent
3884562e8f
commit
481cd40646
6 files changed
+75
-14
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
@@ -31,6 +32,23 @@ func autoAxis(srcW, srcH, boxW, boxH int) (int, int, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// errBoxTooLarge is what a resize answers when a free axis resolves past the
|
||||
// configured maximum thumbnail size.
|
||||
var errBoxTooLarge = errors.New("the resolved box exceeds the maximum thumbnail size")
|
||||
|
||||
// freeAxis is autoAxis for the request, held to the maximum thumbnail size. The
|
||||
// box the request names was checked before anything was read, but a side the
|
||||
// aspect ratio decides is only known once the image is: 0x2048 on a 7680x1
|
||||
// source is a box of 15728640x2048.
|
||||
func (r resizeRequest) freeAxis(srcW, srcH int) (int, int, bool, error) {
|
||||
w, h, resize := autoAxis(srcW, srcH, r.Width, r.Height)
|
||||
if resize && ((r.MaxOutWidth > 0 && w > r.MaxOutWidth) || (r.MaxOutHeight > 0 && h > r.MaxOutHeight)) {
|
||||
return 0, 0, false, errBoxTooLarge
|
||||
}
|
||||
|
||||
return w, h, resize, nil
|
||||
}
|
||||
|
||||
// fitIn scales img into the box with its aspect ratio kept and never enlarges
|
||||
// it, which is imagor's fit-in without the upscale() filter. Not imaging.Fit:
|
||||
// that truncates the scaled side where vips and imagor round it.
|
||||
|
||||
@@ -136,7 +136,10 @@ func (s Thumbnails) pushHandler(w http.ResponseWriter, r *http.Request) {
|
||||
noUpscale := filterPresent(filtersSegment, "no_upscale")
|
||||
// The box and operation are chosen by webdav (the sizing brain); this service
|
||||
// is a dumb, imagor-like executor that just fits within the given box.
|
||||
req := resizeRequest{Width: width, Height: height, Operation: operation, NoUpscale: noUpscale, Smart: smart}
|
||||
req := resizeRequest{
|
||||
Width: width, Height: height, Operation: operation, NoUpscale: noUpscale, Smart: smart,
|
||||
MaxOutWidth: s.maxOutWidth, MaxOutHeight: s.maxOutHeight,
|
||||
}
|
||||
|
||||
// An animated gif is the one source both builds treat the same way: neither
|
||||
// libvips nor the pure Go path has anything better for a paletted animation,
|
||||
@@ -177,6 +180,11 @@ func (s Thumbnails) pushHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
processed, err := resizeSource(src, req)
|
||||
if errors.Is(err, errBoxTooLarge) {
|
||||
writeMaxResolution(w)
|
||||
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
writeNotAcceptable(w, "failed to process image")
|
||||
return
|
||||
@@ -264,8 +272,15 @@ func (s Thumbnails) serveAnimation(w http.ResponseWriter, imgData []byte, req re
|
||||
return
|
||||
}
|
||||
|
||||
resized, err := resizeGIF(g, req)
|
||||
if err != nil {
|
||||
writeMaxResolution(w)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := gif.EncodeAll(&buf, resizeGIF(g, req)); err != nil {
|
||||
if err := gif.EncodeAll(&buf, resized); err != nil {
|
||||
s.logger.Error().Err(err).Msg("could not encode the gif")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, "failed to encode image")
|
||||
@@ -461,10 +476,13 @@ func filterPresent(segment, name string) bool {
|
||||
|
||||
// resizeRequest is what the executor is asked to do. webdav chose the box and
|
||||
// the operation; noUpscale and smart mirror imagor's filters of those names.
|
||||
// MaxOutWidth and MaxOutHeight bound a box a free axis resolves to.
|
||||
type resizeRequest struct {
|
||||
Width int
|
||||
Height int
|
||||
Operation string
|
||||
NoUpscale bool
|
||||
Smart bool
|
||||
Width int
|
||||
Height int
|
||||
Operation string
|
||||
NoUpscale bool
|
||||
Smart bool
|
||||
MaxOutWidth int
|
||||
MaxOutHeight int
|
||||
}
|
||||
@@ -138,7 +138,10 @@ func resizeSourceImaging(src *imagingSource, req resizeRequest) (image.Image, er
|
||||
srcBounds := img.Bounds()
|
||||
|
||||
if req.Width == 0 || req.Height == 0 {
|
||||
boxW, boxH, resize := autoAxis(srcBounds.Dx(), srcBounds.Dy(), req.Width, req.Height)
|
||||
boxW, boxH, resize, err := req.freeAxis(srcBounds.Dx(), srcBounds.Dy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resize {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -42,6 +42,22 @@ var _ = Describe("the push endpoint", func() {
|
||||
|
||||
Expect(rec.Code).To(Equal(http.StatusOK), rec.Body.String())
|
||||
})
|
||||
|
||||
DescribeTable("refuses a free axis the aspect ratio takes above it",
|
||||
func(source []byte, partType string) {
|
||||
rec := postTo(testServiceOutput(100, 100), "/unsafe/0x50/", source, partType)
|
||||
|
||||
Expect(rec.Code).To(Equal(http.StatusUnprocessableEntity), rec.Body.String())
|
||||
},
|
||||
Entry("a still image", pngSource(400, 10), "image/png"),
|
||||
Entry("an animation", gifSource(400, 10, 2), "image/gif"),
|
||||
)
|
||||
|
||||
It("lets a free axis within it through", func() {
|
||||
rec := postTo(testServiceOutput(100, 100), "/unsafe/0x20/", pngSource(40, 10), "image/png")
|
||||
|
||||
Expect(rec.Code).To(Equal(http.StatusOK), rec.Body.String())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("the configured maximum posted image size", func() {
|
||||
|
||||
@@ -53,7 +53,10 @@ func extForSourceFormatVips(src *vips.ImageRef) string {
|
||||
// imagor's no_upscale() filter.
|
||||
func resizeSourceVips(src *vips.ImageRef, req resizeRequest) (*vips.ImageRef, error) {
|
||||
if req.Width == 0 || req.Height == 0 {
|
||||
boxW, boxH, resize := autoAxis(src.Width(), src.Height(), req.Width, req.Height)
|
||||
boxW, boxH, resize, err := req.freeAxis(src.Width(), src.Height())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resize {
|
||||
if err := scaleTo(src, boxW, boxH); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -13,15 +13,18 @@ import (
|
||||
// Floyd-Steinberg dithering. Both backends share it: the frames go through
|
||||
// imaging either way, because neither libvips nor the pure Go path has anything
|
||||
// better for a paletted animation. Code adapted from
|
||||
// https://github.com/willnorris/gifresize.
|
||||
func resizeGIF(m *gif.GIF, req resizeRequest) *gif.GIF {
|
||||
// https://github.com/willnorris/gifresize. The only error is errBoxTooLarge.
|
||||
func resizeGIF(m *gif.GIF, req resizeRequest) (*gif.GIF, error) {
|
||||
srcX, srcY := m.Config.Width, m.Config.Height
|
||||
width, height, operation, noUpscale := req.Width, req.Height, req.Operation, req.NoUpscale
|
||||
|
||||
if width == 0 || height == 0 {
|
||||
resolvedW, resolvedH, resize := autoAxis(srcX, srcY, width, height)
|
||||
resolvedW, resolvedH, resize, err := req.freeAxis(srcX, srcY)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resize {
|
||||
return m
|
||||
return m, nil
|
||||
}
|
||||
width, height, operation = resolvedW, resolvedH, OpStretch
|
||||
}
|
||||
@@ -65,5 +68,5 @@ func resizeGIF(m *gif.GIF, req resizeRequest) *gif.GIF {
|
||||
m.Config.Width, m.Config.Height = b.Dx(), b.Dy()
|
||||
}
|
||||
|
||||
return m
|
||||
return m, nil
|
||||
}
|
||||
Reference in new issue
Block a user