Skip to content

Commit

Permalink
Change cover option to contain to avoid double negative, fix contain …
Browse files Browse the repository at this point in the history
…to not upscale images
  • Loading branch information
SmileyChris committed Aug 6, 2024
1 parent 42fefe7 commit 8f322ff
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 29 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,11 @@ You can also use the following keywords: `tl` (top left), `tr` (top right), `bl`
If crop is `False`, the image will be resized so that it will cover the requested ratio but not cropped down.
This is useful when you want to handle positioning in CSS using `object-fit`.

#### `cover`
#### `contain`

Whether to resize the image to cover the requested ratio or to contain it (when not cropping).
When resizing the image (and not cropping), contain the image within the requested ratio. This ensures it will always fit within the requested dimensions. It also stops the image from being upscaled.

The default is `True`, meaning the image will be resized down to cover the requested ratio (which means the image dimensions may be larger than the requested dimensions).

To rezise the image to always fit within the requested dimensions, set `cover=False`.
The default is `False`, meaning the image will be resized down to cover the requested ratio (which means the image dimensions may be larger than the requested dimensions).

#### `focal_window`

Expand Down
2 changes: 1 addition & 1 deletion easy_images/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"quality": 80,
"ratio": "video",
"crop": True,
"cover": False,
"contain": True,
"densities": [2],
"format": "webp",
}
Expand Down
24 changes: 12 additions & 12 deletions easy_images/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ def scale_image(
target: tuple[int, int],
/,
crop: tuple[float, float] | bool | None = None,
cover: bool = True,
contain: bool = False,
focal_window: tuple[float, float, float, float] | None = None,
):
"""
Scale an image to cover the given dimensions, optionally cropping it around a focal
point or a focal window.
Scale an image to the given dimensions, optionally cropping it around a focal point
or a focal window.
"""
w, h = img.width, img.height

if crop:
cover = True
contain = False

if cover:
# Size image down to cover the dimensions
scale = max(target[0] / w, target[1] / h)
if contain:
# Size image to contain the dimensions, also avoiding upscaling
scale = min(target[0] / w, target[1] / h, 1)
else:
# Size image to contain the dimensions
scale = min(target[0] / w, target[1] / h)
# Scale the image to cover the dimensions
scale = max(target[0] / w, target[1] / h)

# Focal window scaling
if focal_window:
Expand All @@ -52,10 +52,10 @@ def scale_image(
if f_right - f_left > target[0] and f_bottom - f_top > target[1]:
img = img.extract_area(f_left, f_top, f_right - f_left, f_bottom - f_top)
w, h = img.width, h
if cover:
scale = max(target[0] / w, target[1] / h)
if contain:
scale = min(target[0] / w, target[1] / h, 1)
else:
scale = min(target[0] / w, target[1] / h)
scale = max(target[0] / w, target[1] / h)
focal_window = None
# Otherwise, if cropping then set the crop focal point to the center of the
# focal window.
Expand Down
2 changes: 1 addition & 1 deletion easy_images/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def build(
size,
focal_window=options.window,
crop=options.crop,
cover=options.cover,
contain=options.contain,
)
else:
img = source_img
Expand Down
10 changes: 5 additions & 5 deletions easy_images/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@


class ParsedOptions:
__slots__ = ("quality", "crop", "cover", "window", "width", "ratio", "mimetype")
__slots__ = ("quality", "crop", "contain", "window", "width", "ratio", "mimetype")

quality: int
crop: tuple[float, float] | None
cover: bool
contain: bool
window: tuple[float, float, float, float] | None
width: int | None
ratio: float | None
mimetype: str | None

_defaults = {"cover": True}
_defaults = {"contain": False}

def __init__(self, bound=None, string="", /, **options):
if string:
Expand Down Expand Up @@ -112,9 +112,9 @@ def parse_crop(value, **options) -> tuple[float, float] | None:
raise ValueError(f"Invalid crop value {value}")

@staticmethod
def parse_cover(value, **options) -> bool:
def parse_contain(value, **options) -> bool:
if not isinstance(value, bool):
raise ValueError(f"Invalid cover value {value}")
raise ValueError(f"Invalid contain value {value}")
return value

@staticmethod
Expand Down
3 changes: 1 addition & 2 deletions easy_images/types_.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"golden",
"golden_vertical",
]
FitChoices: TypeAlias = Literal["contain", "cover"]

alternative_re = re.compile(r"^(\d+w|\d(?:\.\d)?x)$")

Expand All @@ -42,7 +41,7 @@
class Options(TypedDict, total=False):
quality: int
crop: tuple[float, float] | CropChoices | bool
cover: bool
contain: bool
window: tuple[float, float, float, float] | None
width: int | WidthChoices | None
ratio: float | tuple[float, float] | RatioChoices | None
Expand Down
9 changes: 8 additions & 1 deletion tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ def test_scale():
source = Image.black(1000, 1000)
scaled_cover = scale_image(source, (400, 500))
assert (scaled_cover.width, scaled_cover.height) == (500, 500)
scaled = scale_image(source, (400, 500), cover=False)
scaled = scale_image(source, (400, 500), contain=True)
assert (scaled.width, scaled.height) == (400, 400)
cropped = scale_image(source, (400, 500), crop=True)
assert (cropped.width, cropped.height) == (400, 500)

small_src = Image.black(100, 100)
cropped_upscale = scale_image(small_src, (400, 500), crop=True)
assert (cropped_upscale.width, cropped_upscale.height) == (400, 500)

scaled_not_upscale = scale_image(small_src, (400, 500), contain=True)
assert (scaled_not_upscale.width, scaled_not_upscale.height) == (100, 100)
4 changes: 2 additions & 2 deletions tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ def test_str():
== '{"crop": null, "mimetype": null, "quality": 80, "ratio": 1.7777777777777777, "width": 100, "window": null}'
)
assert (
str(ParsedOptions(width=100, cover=False))
== '{"cover": false, "crop": null, "mimetype": null, "quality": 80, "ratio": null, "width": 100, "window": null}'
str(ParsedOptions(width=100, contain=True))
== '{"contain": true, "crop": null, "mimetype": null, "quality": 80, "ratio": null, "width": 100, "window": null}'
)

0 comments on commit 8f322ff

Please sign in to comment.