Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue when comparing images with different sizes #292

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

BaloghTamas
Copy link
Contributor

We had bumped into an issue when screenshot changes were not detected when the change was only appending to a screenshot, so the size was different, but the intersection of the screenshots remained the same, more details in the issue desription at #258

This PR atteps to fix this issue

@@ -88,12 +88,39 @@ def _clean(self):
shutil.rmtree(self._output)
os.makedirs(self._output)

def _draw_diff_outline(self, failure_file, diff, image_to_draw_on):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utility to highlight changed area on the larger screenshot

draw = ImageDraw.Draw(image_to_draw_on)
draw.rectangle(list(diff), outline=(255, 0, 0))
image_to_draw_on.save(failure_file)

def _is_image_same(self, file1, file2, failure_file):
with Image.open(file1) as im1, Image.open(file2) as im2:
diff_image = ImageChops.difference(im1, im2)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it turned out that ImageChops.difference does not consider different image sizes as different images, but if only compares the intersections https://github.com/python-pillow/Pillow/blob/ee079ae67e7e24ec789d3cc7d180820a70d32fe6/src/libImaging/Chops.c#L74

This is definately not a bug in facebook/screenshot-tests-for-android but IMHO PIL can also claim that this is the intended behaviour. My suggestion is to mitigate this issue here, and check size differences after running ImageChops.difference

def _is_image_same(self, file1, file2, failure_file):
with Image.open(file1) as im1, Image.open(file2) as im2:
diff_image = ImageChops.difference(im1, im2)
try:
diff = diff_image.getbbox()
if diff is None:
if (im1.size != im2.size) or (im1.getbbox() != im2.getbbox()):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in case there is no difference reported by ImageChops.difference it can still happen that their size differ. If their size does not differ, and there is no difference reporter, we can be sure screenshots are the same.

If their size differ, but otherwise ImageChops.difference reported no difference, we can be sure they only differ by the additional pixels that the larger image has, so it makes sense to highlight those as differences.

If one image is not a direct subset of the other ImageChops.difference would report changes, so there is no need to handle those cases.

self.assertEqual((255, 0, 0, 255), im.getpixel((0, 19)))
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 19)))
self.assertEqual((255, 0, 0, 255), im.getpixel((5, 19)))
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 15)))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While debugging tests adding im.show() after these test will quicly open the image with the difference:

Suggested change
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 15)))
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 15)))
im.show()

It requires imagemagick installed, but make verification much quicker. (Also you can right click > View > Double size on the image as these are quite small)

Screenshot from 2021-09-02 10-46-34

Comment on lines +280 to +298
self.assertEqual((0, 0, 255, 255), im.getpixel((0, 0)))
self.assertEqual((0, 0, 255, 255), im.getpixel((9, 0)))
self.assertEqual((0, 0, 255, 255), im.getpixel((5, 0)))
self.assertEqual((0, 0, 255, 255), im.getpixel((0, 5)))

self.assertEqual((0, 0, 255, 255), im.getpixel((0, 9)))
self.assertEqual((0, 0, 255, 255), im.getpixel((9, 9)))
self.assertEqual((0, 0, 255, 255), im.getpixel((5, 9)))
self.assertEqual((0, 0, 255, 255), im.getpixel((5, 5)))

self.assertEqual((255, 0, 0, 255), im.getpixel((0, 10)))
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 10)))
self.assertEqual((255, 0, 0, 255), im.getpixel((5, 10)))
self.assertEqual((255, 0, 0, 255), im.getpixel((0, 15)))

self.assertEqual((255, 0, 0, 255), im.getpixel((0, 19)))
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 19)))
self.assertEqual((255, 0, 0, 255), im.getpixel((5, 19)))
self.assertEqual((255, 0, 0, 255), im.getpixel((9, 15)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking all pixels for the border I think we can get away with just checking the four corners (here and in the other tests)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean checking the corner pixels of the box?

4_corners

I recall that initially I had started with this approach, just checking the four corner pixels, but then during the implementation when I had made a mistake, and the rectange got wider, and lthe right side of the box where rendered ourside of the image, the test still had passed, as the top and bottom lines where still covering all four corner pixels.

4_corners_no_right_line

This is why I had decided to check the pixels in the middle of the lines as well, as this approach would catch that mistake.

8_corners_and_middle

8_corners_and_middleno_right_line

I would suggest to keep these checks to make sure this mistake is covered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, makes sense. We just published a new version that partially addresses the issues this PR fixes, do you mind rebasing onto that? It seems like ImageChops.difference in general is not very suitable for ARGB PNGs, unfortunately, but I'm not sure off the top of my head on what would be an easy / better alternative within Pillow.

facebook-github-bot pushed a commit that referenced this pull request Feb 3, 2022
… check

Summary: For some odd reason `ImageChops.difference` does not work at all with RGBA formatted images, so this forces images to be in RGB format prior to diffing (which may also indicate that we're dropping alpha only changes). This also adds a change to check for image dimension differences to partially address the concerns in #292

Differential Revision: D33961675

fbshipit-source-id: 893cb07568893e74a9717b6c90fc9b1ab065feb3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants