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 rounding error on RGB to L conversion #4320

Merged
merged 3 commits into from
Jan 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file modified Tests/images/imagedraw_floodfill_L.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Tests/images/imagedraw_polygon_kite_L.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions Tests/test_image_entropy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ def entropy(mode):
return hopper(mode).entropy()

self.assertAlmostEqual(entropy("1"), 0.9138803254693582)
self.assertAlmostEqual(entropy("L"), 7.06650513081286)
self.assertAlmostEqual(entropy("I"), 7.06650513081286)
self.assertAlmostEqual(entropy("F"), 7.06650513081286)
self.assertAlmostEqual(entropy("L"), 7.063008716585465)
self.assertAlmostEqual(entropy("I"), 7.063008716585465)
self.assertAlmostEqual(entropy("F"), 7.063008716585465)
self.assertAlmostEqual(entropy("P"), 5.0530452472519745)
self.assertAlmostEqual(entropy("RGB"), 8.821286587714319)
self.assertAlmostEqual(entropy("RGBA"), 7.42724306524488)
Expand Down
6 changes: 3 additions & 3 deletions Tests/test_image_getdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ def getdata(mode):
return data[0], len(data), len(list(data))

self.assertEqual(getdata("1"), (0, 960, 960))
self.assertEqual(getdata("L"), (16, 960, 960))
self.assertEqual(getdata("I"), (16, 960, 960))
self.assertEqual(getdata("F"), (16.0, 960, 960))
self.assertEqual(getdata("L"), (17, 960, 960))
self.assertEqual(getdata("I"), (17, 960, 960))
self.assertEqual(getdata("F"), (17.0, 960, 960))
self.assertEqual(getdata("RGB"), ((11, 13, 52), 960, 960))
self.assertEqual(getdata("RGBA"), ((11, 13, 52, 255), 960, 960))
self.assertEqual(getdata("CMYK"), ((244, 242, 203, 0), 960, 960))
Expand Down
8 changes: 4 additions & 4 deletions Tests/test_image_getextrema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ def extrema(mode):
return hopper(mode).getextrema()

self.assertEqual(extrema("1"), (0, 255))
self.assertEqual(extrema("L"), (0, 255))
self.assertEqual(extrema("I"), (0, 255))
self.assertEqual(extrema("F"), (0, 255))
self.assertEqual(extrema("L"), (1, 255))
self.assertEqual(extrema("I"), (1, 255))
self.assertEqual(extrema("F"), (1, 255))
self.assertEqual(extrema("P"), (0, 225)) # fixed palette
self.assertEqual(extrema("RGB"), ((0, 255), (0, 255), (0, 255)))
self.assertEqual(extrema("RGBA"), ((0, 255), (0, 255), (0, 255), (255, 255)))
self.assertEqual(extrema("CMYK"), ((0, 255), (0, 255), (0, 255), (0, 0)))
self.assertEqual(extrema("I;16"), (0, 255))
self.assertEqual(extrema("I;16"), (1, 255))

def test_true_16(self):
with Image.open("Tests/images/16_bit_noise.tif") as im:
Expand Down
6 changes: 3 additions & 3 deletions Tests/test_image_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ def histogram(mode):
return len(h), min(h), max(h)

self.assertEqual(histogram("1"), (256, 0, 10994))
self.assertEqual(histogram("L"), (256, 0, 638))
self.assertEqual(histogram("I"), (256, 0, 638))
self.assertEqual(histogram("F"), (256, 0, 638))
self.assertEqual(histogram("L"), (256, 0, 662))
self.assertEqual(histogram("I"), (256, 0, 662))
self.assertEqual(histogram("F"), (256, 0, 662))
self.assertEqual(histogram("P"), (256, 0, 1871))
self.assertEqual(histogram("RGB"), (768, 4, 675))
self.assertEqual(histogram("RGBA"), (1024, 0, 16384))
Expand Down
6 changes: 3 additions & 3 deletions Tests/test_imagecolor.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,20 +165,20 @@ def test_rounding_errors(self):

self.assertEqual(0, ImageColor.getcolor("black", "L"))
self.assertEqual(255, ImageColor.getcolor("white", "L"))
self.assertEqual(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L"))
self.assertEqual(163, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L"))
Image.new("L", (1, 1), "white")

self.assertEqual(0, ImageColor.getcolor("black", "1"))
self.assertEqual(255, ImageColor.getcolor("white", "1"))
# The following test is wrong, but is current behavior
# The correct result should be 255 due to the mode 1
self.assertEqual(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1"))
self.assertEqual(163, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1"))
# Correct behavior
# self.assertEqual(
# 255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1"))
Image.new("1", (1, 1), "white")

self.assertEqual((0, 255), ImageColor.getcolor("black", "LA"))
self.assertEqual((255, 255), ImageColor.getcolor("white", "LA"))
self.assertEqual((162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA"))
self.assertEqual((163, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA"))
Image.new("LA", (1, 1), "white")
4 changes: 3 additions & 1 deletion src/PIL/ImageColor.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ def getcolor(color, mode):

if Image.getmodebase(mode) == "L":
r, g, b = color
color = (r * 299 + g * 587 + b * 114) // 1000
# ITU-R Recommendation 601-2 for nonlinear RGB
# scaled to 24 bits to match the convert's implementation.
color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
if mode[-1] == "A":
return (color, alpha)
else:
Expand Down
3 changes: 2 additions & 1 deletion src/libImaging/Convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
#define L(rgb)\
((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114)
Copy link
Member

@radarhere radarhere Dec 31, 2019

Choose a reason for hiding this comment

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

If you've changed ImageColor, shouldn't this be changed as well for consistency?

color = (r * 299 + g * 587 + b * 114 + 500) // 1000

Copy link
Member Author

@homm homm Dec 31, 2019

Choose a reason for hiding this comment

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

Not the "L" macro itself should be fixed. This macro is used for F mode for example, where no rounding needed. Instead, every place where "L" is used should be analyzed. This is a bit more work that needs more attention and I haven't much time today to do this before the release.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I've fixed ImageColor.getcolor implementation.

#define L24(rgb)\
((rgb)[0]*19595 + (rgb)[1]*38470 + (rgb)[2]*7471)
((rgb)[0]*19595 + (rgb)[1]*38470 + (rgb)[2]*7471 + 0x8000)


#ifndef round
double round(double x) {
Expand Down