diff --git a/Tests/images/drawing_roundDown.emf b/Tests/images/drawing_roundDown.emf new file mode 100644 index 000000000..6c3e20248 Binary files /dev/null and b/Tests/images/drawing_roundDown.emf differ diff --git a/Tests/images/hopper_roundDown.bmp b/Tests/images/hopper_roundDown.bmp new file mode 100644 index 000000000..62aada050 Binary files /dev/null and b/Tests/images/hopper_roundDown.bmp differ diff --git a/Tests/images/hopper_roundDown_2.tif b/Tests/images/hopper_roundDown_2.tif new file mode 100644 index 000000000..ac8cd057d Binary files /dev/null and b/Tests/images/hopper_roundDown_2.tif differ diff --git a/Tests/images/hopper_roundDown_3.tif b/Tests/images/hopper_roundDown_3.tif new file mode 100644 index 000000000..0542fab9a Binary files /dev/null and b/Tests/images/hopper_roundDown_3.tif differ diff --git a/Tests/images/hopper_roundDown_None.tif b/Tests/images/hopper_roundDown_None.tif new file mode 100644 index 000000000..21c40e8fe Binary files /dev/null and b/Tests/images/hopper_roundDown_None.tif differ diff --git a/Tests/images/hopper_roundUp_2.tif b/Tests/images/hopper_roundUp_2.tif new file mode 100644 index 000000000..e38541c5d Binary files /dev/null and b/Tests/images/hopper_roundUp_2.tif differ diff --git a/Tests/images/hopper_roundUp_3.tif b/Tests/images/hopper_roundUp_3.tif new file mode 100644 index 000000000..af6c96bd4 Binary files /dev/null and b/Tests/images/hopper_roundUp_3.tif differ diff --git a/Tests/images/hopper_roundUp_None.tif b/Tests/images/hopper_roundUp_None.tif new file mode 100644 index 000000000..b98635108 Binary files /dev/null and b/Tests/images/hopper_roundUp_None.tif differ diff --git a/Tests/images/iptc_roundDown.jpg b/Tests/images/iptc_roundDown.jpg new file mode 100644 index 000000000..f98206f18 Binary files /dev/null and b/Tests/images/iptc_roundDown.jpg differ diff --git a/Tests/images/iptc_roundUp.jpg b/Tests/images/iptc_roundUp.jpg new file mode 100644 index 000000000..68ac20b71 Binary files /dev/null and b/Tests/images/iptc_roundUp.jpg differ diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index ec651ae8c..b97a84227 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -71,6 +71,27 @@ class TestFileBmp(PillowTestCase): self.assertEqual(im.size, reloaded.size) self.assertEqual(reloaded.format, "JPEG") + def test_load_dpi_rounding(self): + # Round up + im = Image.open('Tests/images/hopper.bmp') + self.assertEqual(im.info["dpi"], (96, 96)) + + # Round down + im = Image.open('Tests/images/hopper_roundDown.bmp') + self.assertEqual(im.info["dpi"], (72, 72)) + + def test_save_dpi_rounding(self): + outfile = self.tempfile("temp.bmp") + im = Image.open('Tests/images/hopper.bmp') + + im.save(outfile, dpi=(72.2, 72.2)) + reloaded = Image.open(outfile) + self.assertEqual(reloaded.info["dpi"], (72, 72)) + + im.save(outfile, dpi=(72.8, 72.8)) + reloaded = Image.open(outfile) + self.assertEqual(reloaded.info["dpi"], (73, 73)) + def test_load_dib(self): # test for #1293, Imagegrab returning Unsupported Bitfields Format im = Image.open('Tests/images/clipboard.dib') diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 712a36cd5..25c3f754a 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -524,6 +524,27 @@ class TestFileJpeg(PillowTestCase): reloaded.load() self.assertEqual(im.info['dpi'], reloaded.info['dpi']) + def test_load_dpi_rounding(self): + # Round up + im = Image.open('Tests/images/iptc_roundUp.jpg') + self.assertEqual(im.info["dpi"], (44, 44)) + + # Round down + im = Image.open('Tests/images/iptc_roundDown.jpg') + self.assertEqual(im.info["dpi"], (2, 2)) + + def test_save_dpi_rounding(self): + outfile = self.tempfile("temp.jpg") + im = Image.open('Tests/images/hopper.jpg') + + im.save(outfile, dpi=(72.2, 72.2)) + reloaded = Image.open(outfile) + self.assertEqual(reloaded.info["dpi"], (72, 72)) + + im.save(outfile, dpi=(72.8, 72.8)) + reloaded = Image.open(outfile) + self.assertEqual(reloaded.info["dpi"], (73, 73)) + def test_dpi_tuple_from_exif(self): # Arrange # This Photoshop CC 2017 image has DPI in EXIF not metadata diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 580521752..6a061fe6a 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -389,6 +389,24 @@ class TestFilePng(PillowTestCase): im = roundtrip(im, dpi=(100, 100)) self.assertEqual(im.info["dpi"], (100, 100)) + def test_load_dpi_rounding(self): + # Round up + im = Image.open(TEST_PNG_FILE) + self.assertEqual(im.info["dpi"], (96, 96)) + + # Round down + im = Image.open("Tests/images/icc_profile_none.png") + self.assertEqual(im.info["dpi"], (72, 72)) + + def test_save_dpi_rounding(self): + im = Image.open(TEST_PNG_FILE) + + im = roundtrip(im, dpi=(72.2, 72.2)) + self.assertEqual(im.info["dpi"], (72, 72)) + + im = roundtrip(im, dpi=(72.8, 72.8)) + self.assertEqual(im.info["dpi"], (73, 73)) + def test_roundtrip_text(self): # Check text roundtripping diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 34911ed50..950a449f2 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -126,6 +126,30 @@ class TestFileTiff(PillowTestCase): im._setup() self.assertEqual(im.info['dpi'], (71., 71.)) + def test_load_dpi_rounding(self): + for resolutionUnit, dpi in ((None, (72, 73)), + (2, (72, 73)), + (3, (183, 185))): + im = Image.open( + "Tests/images/hopper_roundDown_"+str(resolutionUnit)+".tif") + self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit) + self.assertEqual(im.info['dpi'], (dpi[0], dpi[0])) + + im = Image.open("Tests/images/hopper_roundUp_"+str(resolutionUnit)+".tif") + self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit) + self.assertEqual(im.info['dpi'], (dpi[1], dpi[1])) + + def test_save_dpi_rounding(self): + outfile = self.tempfile("temp.tif") + im = Image.open("Tests/images/hopper.tif") + + for dpi in (72.2, 72.8): + im.save(outfile, dpi=(dpi, dpi)) + + reloaded = Image.open(outfile) + reloaded.load() + self.assertEqual((round(dpi), round(dpi)), reloaded.info['dpi']) + def test_save_setting_missing_resolution(self): b = BytesIO() Image.open("Tests/images/10ct_32bit_128.tiff").save( diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 146888491..e3c6063f2 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -45,6 +45,15 @@ class TestFileWmf(PillowTestCase): # Restore the state before this test WmfImagePlugin.register_handler(None) + def test_load_dpi_rounding(self): + # Round up + im = Image.open('Tests/images/drawing.emf') + self.assertEqual(im.info["dpi"], 1424) + + # Round down + im = Image.open('Tests/images/drawing_roundDown.emf') + self.assertEqual(im.info["dpi"], 1426) + def test_save(self): im = hopper() diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 9a82e526a..3ca293a94 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -27,7 +27,6 @@ from . import Image, ImageFile, ImagePalette from ._binary import i8, i16le as i16, i32le as i32, \ o8, o16le as o16, o32le as o32 -import math # __version__ is deprecated and will be removed in a future version. Use # PIL.__version__ instead. @@ -121,8 +120,7 @@ class BmpImageFile(ImageFile.ImageFile): file_info['colors'] = i32(header_data[28:32]) file_info['palette_padding'] = 4 self.info["dpi"] = tuple( - map(lambda x: int(math.ceil(x / 39.3701)), - file_info['pixels_per_meter'])) + int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter']) if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52: for idx, mask in enumerate(['r_mask', @@ -311,7 +309,7 @@ def _save(im, fp, filename, bitmap_header=True): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) + ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi)) stride = ((im.size[0]*bits+7)//8+3) & (~3) header = 40 # or 64 for OS/2 version 2 diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 541b84ee8..d42c5128d 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -160,13 +160,13 @@ def APP(self, marker): resolution_unit = exif[0x0128] x_resolution = exif[0x011A] try: - dpi = x_resolution[0] / x_resolution[1] + dpi = float(x_resolution[0]) / x_resolution[1] except TypeError: dpi = x_resolution if resolution_unit == 3: # cm # 1 dpcm = 2.54 dpi dpi *= 2.54 - self.info["dpi"] = dpi, dpi + self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5) except (KeyError, SyntaxError, ZeroDivisionError): # SyntaxError for invalid/unreadable exif # KeyError for dpi not included diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 34c6f39de..0b722b447 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1260,11 +1260,11 @@ class TiffImageFile(ImageFile.ImageFile): if xres and yres: resunit = self.tag_v2.get(RESOLUTION_UNIT) if resunit == 2: # dots per inch - self.info["dpi"] = xres, yres + self.info["dpi"] = int(xres + 0.5), int(yres + 0.5) elif resunit == 3: # dots per centimeter. convert to dpi - self.info["dpi"] = xres * 2.54, yres * 2.54 + self.info["dpi"] = int(xres * 2.54 + 0.5), int(yres * 2.54 + 0.5) elif resunit is None: # used to default to 1, but now 2) - self.info["dpi"] = xres, yres + self.info["dpi"] = int(xres + 0.5), int(yres + 0.5) # For backward compatibility, # we also preserve the old behavior self.info["resolution"] = xres, yres @@ -1475,8 +1475,8 @@ def _save(im, fp, filename): dpi = im.encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 - ifd[X_RESOLUTION] = dpi[0] - ifd[Y_RESOLUTION] = dpi[1] + ifd[X_RESOLUTION] = int(dpi[0] + 0.5) + ifd[Y_RESOLUTION] = int(dpi[1] + 0.5) if bits != (1,): ifd[BITSPERSAMPLE] = bits diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 06eb851c9..413bd1847 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -131,8 +131,8 @@ class WmfStubImageFile(ImageFile.StubImageFile): size = x1 - x0, y1 - y0 # calculate dots per inch from bbox and frame - xdpi = 2540 * (x1 - y0) // (frame[2] - frame[0]) - ydpi = 2540 * (y1 - y0) // (frame[3] - frame[1]) + xdpi = int(2540.0 * (x1 - y0) / (frame[2] - frame[0]) + 0.5) + ydpi = int(2540.0 * (y1 - y0) / (frame[3] - frame[1]) + 0.5) self.info["wmf_bbox"] = x0, y0, x1, y1