Closed charlie-collard closed 5 years ago
Thank you! I will review the changes next weekend.
This breaks one test, TestFill/Fill_4x4_2x8_Top_Nearest and I'm afraid I'm not sure why
It's because the new algorithm crops the source image to 1x4px first. So it takes a second pixel column and resizes it to 2x8px. The old algorithm provides a more accurate result for such a small image. Here's a visual representation using letters in place of pixels.
Source image New result Old result
ABCD BB BC
EFGH BB BC
IKLM FF FG
NPQR FF FG
KK KL
KK KL
PP PQ
PP PQ
Ah I understand, it's because the new algorithm crops so that the pre-resize aspect ratio matches the post-resize aspect ratio. This means that it selects the 1x4 BFKP column because it matches the 0.25 aspect ratio of the destination image.
In order to get the more accurate result, I think we'd have to crop to subpixels before we did the resize. The 1x4 selection would be half column BFKP and half column CGLQ. Upscale these subpixels and, in theory, out pops the correct 2x8 image. In reality, I think we'd have to crop to include any pixel which is fractionally in the selection, resize to the desired height, then crop again to the desired width.
This is getting a bit convoluted for a fairly minor performance gain. Should I try to implement this? I'd be happy to give it a go, but if you think the final code would be too messy for the library then feel free to just close the pull request :)
Sorry for the delayed response.
Here's another example where the old algorithm does better job. The 3x3 checkers image is scaled to 100x125px. Ideally the individual pixels should be transformed to squares.
func main() {
src := &image.Gray{
Rect: image.Rect(0, 0, 3, 3),
Stride: 3,
Pix: []uint8{
0, 255, 0,
255, 0, 255,
0, 255, 0,
},
}
dst := imaging.Fill(src, 100, 125, imaging.TopLeft, imaging.NearestNeighbor)
imaging.Save(dst, "dst.jpg")
}
Old result | New result |
---|---|
Ah I understand, it's because the new algorithm crops so that the pre-resize aspect ratio matches the post-resize aspect ratio. This means that it selects the 1x4 BFKP column because it matches the 0.25 aspect ratio of the destination image.
In order to get the more accurate result, I think we'd have to crop to subpixels before we did the resize. The 1x4 selection would be half column BFKP and half column CGLQ. Upscale these subpixels and, in theory, out pops the correct 2x8 image. In reality, I think we'd have to crop to include any pixel which is fractionally in the selection, resize to the desired height, then crop again to the desired width.
This is getting a bit convoluted for a fairly minor performance gain. Should I try to implement this? I'd be happy to give it a go, but if you think the final code would be too messy for the library then feel free to just close the pull request :)
I think this would work. On the other hand the additional cropping step is the downside of this approach.
After doing some tests, it looks like the new algorithm works fine most of the times. If the size of the original image is not too small, we have enough pixels in both directions to make the initial crop with the desired aspect ratio. We could fallback to the old algorithm for images smaller than, say, 100x100px.
if srcW >= 100 && srcH >= 100 {
cropAndResize(...)
} else {
resizeAndCrop(...)
}
I've added the fallback, and updated the documentation. I did try the subpixel method but was still getting inconsistent results, so I think this is a good solution.
I'm guessing the two new functions need tests, it seems to me that the current tests for Fill could also be used for resizeAndCrop as it has identical functionality to the old Fill.
As for cropAndResize, I'm unsure. If it's meant to be used solely for large images then maybe a few golden examples will do?
Sure, for resizeAndCrop we can use the existing test cases. As for cropAndResize, despite the fact that it's meant to be used with larger images, in tests we just want to ensure that the algorithm works as intended, so I think we still can use a few small-sized test cases. A golden test would be nice to have too.
Thank you!
83 Crop before resizing in the Fill function for the sake of efficiency.
This breaks one test,
TestFill/Fill_4x4_2x8_Top_Nearest
and I'm afraid I'm not sure why, I'm pretty sure the code is correct as all other tests pass, but maybe there's a strange edge case I'm not thinking of.I've also added some benchmarks for the Fill function, on my machine I get speedups of around 8x:
Before
After