no_upscale() cancels fill()'s canvas expansion, so a no-upscale pad does not pad
Combining no_upscale() with fill() on a fit-in request returns the content size instead of the requested box. The padding disappears entirely, so it is the canvas that is wrong and not just the bars.
Repro on 7.7.7, using one of your own test fixtures as the source so it runs as is:
docker run -d --name tb -p 8877:8888 ghcr.io/thumbor/thumbor:latest
S="raw.githubusercontent.com/thumbor/thumbor/master/tests/fixtures/images/20x20.jpg"
for f in "no_upscale():fill(ff0000)" "fill(ff0000):no_upscale()" "fill(ff0000)"; do
curl -s "http://localhost:8877/unsafe/fit-in/400x300/filters:${f}:format(png)/$S" -o out.png
printf '%-30s -> %s\n' "$f" "$(identify -format '%wx%h' out.png)"
doneno_upscale():fill(ff0000) -> 20x20
fill(ff0000):no_upscale() -> 20x20
fill(ff0000) -> 400x300The third line is the control. fill() on its own does expand the canvas to the requested box, and adding no_upscale() takes that away. Both filter orders behave the same.
Looking at the source, no_upscale runs in PHASE_AFTER_LOAD and does not cap the resize, it rewrites the request:
self.context.request.width = min(self.context.request.width, image_size[0])
self.context.request.height = min(self.context.request.height, image_size[1])fill then builds its canvas from those same two values:
target_width = (
self.context.request.width
if self.context.request.width != 0
else self.engine.size[0]
)So by the time fill runs the target box is already the source size, and there is nothing left to pad.
If that reading is right it is not specific to fill, anything downstream reading request.width/height as the target box would see the clamped value rather than the requested one. I have only measured fill.
I do not have a fix to propose, since no_upscale clamping the request is presumably deliberate for the plain resize case. Maybe the clamp needs to apply to the resize step and leave the requested box alone. I work at the URL layer against thumbor and have not read enough of the transform pipeline to say.
For what it is worth, imagor takes the same filter grammar and pads correctly for both orders, which is what made me look at this in the first place.
Source: thumbor/thumbor