Merge pull request #3709 from radarhere/dpi

Consistent DPI rounding
This commit is contained in:
Hugo 2019-03-30 11:57:30 +02:00 committed by GitHub
commit 2fdd3e1fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 104 additions and 13 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -71,6 +71,27 @@ class TestFileBmp(PillowTestCase):
self.assertEqual(im.size, reloaded.size) self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "JPEG") 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): def test_load_dib(self):
# test for #1293, Imagegrab returning Unsupported Bitfields Format # test for #1293, Imagegrab returning Unsupported Bitfields Format
im = Image.open('Tests/images/clipboard.dib') im = Image.open('Tests/images/clipboard.dib')

View File

@ -524,6 +524,27 @@ class TestFileJpeg(PillowTestCase):
reloaded.load() reloaded.load()
self.assertEqual(im.info['dpi'], reloaded.info['dpi']) 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): def test_dpi_tuple_from_exif(self):
# Arrange # Arrange
# This Photoshop CC 2017 image has DPI in EXIF not metadata # This Photoshop CC 2017 image has DPI in EXIF not metadata

View File

@ -389,6 +389,24 @@ class TestFilePng(PillowTestCase):
im = roundtrip(im, dpi=(100, 100)) im = roundtrip(im, dpi=(100, 100))
self.assertEqual(im.info["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): def test_roundtrip_text(self):
# Check text roundtripping # Check text roundtripping

View File

@ -126,6 +126,30 @@ class TestFileTiff(PillowTestCase):
im._setup() im._setup()
self.assertEqual(im.info['dpi'], (71., 71.)) 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): def test_save_setting_missing_resolution(self):
b = BytesIO() b = BytesIO()
Image.open("Tests/images/10ct_32bit_128.tiff").save( Image.open("Tests/images/10ct_32bit_128.tiff").save(

View File

@ -45,6 +45,15 @@ class TestFileWmf(PillowTestCase):
# Restore the state before this test # Restore the state before this test
WmfImagePlugin.register_handler(None) 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): def test_save(self):
im = hopper() im = hopper()

View File

@ -27,7 +27,6 @@
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16le as i16, i32le as i32, \ from ._binary import i8, i16le as i16, i32le as i32, \
o8, o16le as o16, o32le as o32 o8, o16le as o16, o32le as o32
import math
# __version__ is deprecated and will be removed in a future version. Use # __version__ is deprecated and will be removed in a future version. Use
# PIL.__version__ instead. # PIL.__version__ instead.
@ -121,8 +120,7 @@ class BmpImageFile(ImageFile.ImageFile):
file_info['colors'] = i32(header_data[28:32]) file_info['colors'] = i32(header_data[28:32])
file_info['palette_padding'] = 4 file_info['palette_padding'] = 4
self.info["dpi"] = tuple( self.info["dpi"] = tuple(
map(lambda x: int(math.ceil(x / 39.3701)), int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter'])
file_info['pixels_per_meter']))
if file_info['compression'] == self.BITFIELDS: if file_info['compression'] == self.BITFIELDS:
if len(header_data) >= 52: if len(header_data) >= 52:
for idx, mask in enumerate(['r_mask', 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)) dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches # 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) stride = ((im.size[0]*bits+7)//8+3) & (~3)
header = 40 # or 64 for OS/2 version 2 header = 40 # or 64 for OS/2 version 2

View File

@ -160,13 +160,13 @@ def APP(self, marker):
resolution_unit = exif[0x0128] resolution_unit = exif[0x0128]
x_resolution = exif[0x011A] x_resolution = exif[0x011A]
try: try:
dpi = x_resolution[0] / x_resolution[1] dpi = float(x_resolution[0]) / x_resolution[1]
except TypeError: except TypeError:
dpi = x_resolution dpi = x_resolution
if resolution_unit == 3: # cm if resolution_unit == 3: # cm
# 1 dpcm = 2.54 dpi # 1 dpcm = 2.54 dpi
dpi *= 2.54 dpi *= 2.54
self.info["dpi"] = dpi, dpi self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
except (KeyError, SyntaxError, ZeroDivisionError): except (KeyError, SyntaxError, ZeroDivisionError):
# SyntaxError for invalid/unreadable exif # SyntaxError for invalid/unreadable exif
# KeyError for dpi not included # KeyError for dpi not included

View File

@ -1260,11 +1260,11 @@ class TiffImageFile(ImageFile.ImageFile):
if xres and yres: if xres and yres:
resunit = self.tag_v2.get(RESOLUTION_UNIT) resunit = self.tag_v2.get(RESOLUTION_UNIT)
if resunit == 2: # dots per inch 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 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) 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, # For backward compatibility,
# we also preserve the old behavior # we also preserve the old behavior
self.info["resolution"] = xres, yres self.info["resolution"] = xres, yres
@ -1475,8 +1475,8 @@ def _save(im, fp, filename):
dpi = im.encoderinfo.get("dpi") dpi = im.encoderinfo.get("dpi")
if dpi: if dpi:
ifd[RESOLUTION_UNIT] = 2 ifd[RESOLUTION_UNIT] = 2
ifd[X_RESOLUTION] = dpi[0] ifd[X_RESOLUTION] = int(dpi[0] + 0.5)
ifd[Y_RESOLUTION] = dpi[1] ifd[Y_RESOLUTION] = int(dpi[1] + 0.5)
if bits != (1,): if bits != (1,):
ifd[BITSPERSAMPLE] = bits ifd[BITSPERSAMPLE] = bits

View File

@ -131,8 +131,8 @@ class WmfStubImageFile(ImageFile.StubImageFile):
size = x1 - x0, y1 - y0 size = x1 - x0, y1 - y0
# calculate dots per inch from bbox and frame # calculate dots per inch from bbox and frame
xdpi = 2540 * (x1 - y0) // (frame[2] - frame[0]) xdpi = int(2540.0 * (x1 - y0) / (frame[2] - frame[0]) + 0.5)
ydpi = 2540 * (y1 - y0) // (frame[3] - frame[1]) ydpi = int(2540.0 * (y1 - y0) / (frame[3] - frame[1]) + 0.5)
self.info["wmf_bbox"] = x0, y0, x1, y1 self.info["wmf_bbox"] = x0, y0, x1, y1