Skip to content

Commit

Permalink
Merge pull request #3709 from radarhere/dpi
Browse files Browse the repository at this point in the history
Consistent DPI rounding
  • Loading branch information
hugovk committed Mar 30, 2019
2 parents 2adfea9 + c96cdb5 commit 2fdd3e1
Show file tree
Hide file tree
Showing 19 changed files with 104 additions and 13 deletions.
Binary file added Tests/images/drawing_roundDown.emf
Binary file not shown.
Binary file added Tests/images/hopper_roundDown.bmp
Binary file not shown.
Binary file added Tests/images/hopper_roundDown_2.tif
Binary file not shown.
Binary file added Tests/images/hopper_roundDown_3.tif
Binary file not shown.
Binary file added Tests/images/hopper_roundDown_None.tif
Binary file not shown.
Binary file added Tests/images/hopper_roundUp_2.tif
Binary file not shown.
Binary file added Tests/images/hopper_roundUp_3.tif
Binary file not shown.
Binary file added Tests/images/hopper_roundUp_None.tif
Binary file not shown.
Binary file added Tests/images/iptc_roundDown.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/iptc_roundUp.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions Tests/test_file_bmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ def test_save_bmp_with_dpi(self):
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')
Expand Down
21 changes: 21 additions & 0 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,27 @@ def test_save_tiff_with_dpi(self):
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
Expand Down
18 changes: 18 additions & 0 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,24 @@ def test_roundtrip_dpi(self):
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

Expand Down
24 changes: 24 additions & 0 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,30 @@ def test_int_resolution(self):
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(
Expand Down
9 changes: 9 additions & 0 deletions Tests/test_file_wmf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ def save(self, im, fp, filename):
# 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()

Expand Down
6 changes: 2 additions & 4 deletions src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -121,8 +120,7 @@ def _bitmap(self, header=0, offset=0):
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',
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/JpegImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1260,11 +1260,11 @@ def _setup(self):
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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/WmfImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ def _open(self):
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

Expand Down

0 comments on commit 2fdd3e1

Please sign in to comment.