Skip to content

Commit

Permalink
Use default resolution when scaling image size
Browse files Browse the repository at this point in the history
Scaling the images based on their resolution is good when the image size
is unconstrained, it prevents images with a high resolution from
appearing larger than images with a lower resolution but the same pixel
size.

When the image size is constrained, ignore resolution and scale image to
the constrained size.

The default DPI is now used in the project, moved the constant from the
tests to html2docx/image.py and renamed it to `DEFAULT_DPI` to better
describe it.

Reviewed-by: Jordan Arentsen
  • Loading branch information
francoisfreitag committed Feb 4, 2020
1 parent 0439b6a commit 2922d59
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 18 deletions.
21 changes: 17 additions & 4 deletions html2docx/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# In LibreOffice, the maximum height for an image is capped to USABLE_HEIGHT.
USABLE_HEIGHT = Inches(8.1)
USABLE_WIDTH = Inches(5.8)
DEFAULT_DPI = 72

MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10 MiB

Expand Down Expand Up @@ -104,11 +105,23 @@ def image_size(
"""
image = Image.from_blob(image_buffer.getbuffer())

height = image.px_height if height_px is None else height_px
width = image.px_width if width_px is None else width_px
# Normalize image size to inches.
# - Without a specified pixel size, images are their actual pixel size, so that
# images of the same pixel size appear the same size in the document, regardless
# of their resolution.
# - With a specified pixel size, images should take the specified size, regardless
# of their resolution.
if height_px is None:
height = image.px_height / image.vert_dpi
else:
height = height_px / DEFAULT_DPI
if width_px is None:
width = image.px_width / image.horz_dpi
else:
width = width_px / DEFAULT_DPI

height = Inches(height / image.vert_dpi)
width = Inches(width / image.horz_dpi)
height = Inches(height)
width = Inches(width)

size = {}
if width > USABLE_WIDTH:
Expand Down
4 changes: 2 additions & 2 deletions tests/data/img_height.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"shapes": [
{
"type": 3,
"width": 2857500,
"height": 2857500
"width": 3810000,
"height": 3810000
}
]
}
Expand Down
4 changes: 2 additions & 2 deletions tests/data/img_ratio.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"shapes": [
{
"type": 3,
"width": 190500,
"height": 95250
"width": 254000,
"height": 127000
}
]
}
Expand Down
4 changes: 2 additions & 2 deletions tests/data/img_width.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"shapes": [
{
"type": 3,
"width": 2857500,
"height": 2857500
"width": 3810000,
"height": 3810000
}
]
}
Expand Down
26 changes: 20 additions & 6 deletions tests/test_image_size.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

from docx.shared import Inches

from html2docx.image import USABLE_HEIGHT, USABLE_WIDTH, image_size
from html2docx.image import DEFAULT_DPI, USABLE_HEIGHT, USABLE_WIDTH, image_size

from .utils import DPI, PROJECT_DIR, generate_image
from .utils import PROJECT_DIR, generate_image

broken_image = PROJECT_DIR / "html2docx" / "image-broken.png"
broken_image_bytes = broken_image.read_bytes()


def inches_to_px(inches: int, dpi: int = DPI) -> int:
def inches_to_px(inches: int, dpi: int = DEFAULT_DPI) -> int:
return ceil(inches / Inches(1) * dpi)


def px_to_inches(px: int, dpi: int = DPI) -> int:
def px_to_inches(px: int, dpi: int = DEFAULT_DPI) -> int:
return ceil(px * Inches(1) / dpi)


Expand Down Expand Up @@ -77,15 +77,29 @@ def test_resize_exceeds_height():
assert size == {"height": USABLE_HEIGHT}


def test_dpi_width():
def test_no_pixel_size_uses_dpi_width():
width_px = inches_to_px(USABLE_WIDTH, 300)
image = generate_image(width=width_px, height=1, dpi=(300, 300))
size = image_size(image)
assert size == {}


def test_dpi_height():
def test_no_pixel_size_uses_dpi_height():
height_px = inches_to_px(USABLE_HEIGHT, 300)
image = generate_image(width=1, height=height_px, dpi=(300, 300))
size = image_size(image)
assert size == {}


def test_pixel_size_specified_ignores_dpi_width():
width_px = inches_to_px(Inches(1))
image = generate_image(width=width_px, height=1, dpi=(300, 300))
size = image_size(image, width_px=width_px)
assert size == {"width": Inches(1)}


def test_pixel_size_specified_ignores_dpi_height():
height_px = inches_to_px(Inches(1))
image = generate_image(width=1, height=height_px, dpi=(300, 300))
size = image_size(image, height_px=height_px)
assert size == {"height": Inches(1)}
5 changes: 3 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

from PIL import Image

from html2docx.image import DEFAULT_DPI

TEST_DIR = pathlib.Path(__file__).parent.resolve(strict=True)
PROJECT_DIR = TEST_DIR.parent
DPI = 72


def generate_image(width: int, height: int, dpi=(DPI, DPI)) -> BytesIO:
def generate_image(width: int, height: int, dpi=(DEFAULT_DPI, DEFAULT_DPI)) -> BytesIO:
data = BytesIO()
with Image.new("L", (width, height)) as image:
image.save(data, format="png", dpi=dpi)
Expand Down

0 comments on commit 2922d59

Please sign in to comment.