Skip to content

Commit

Permalink
[lib,test] Fixed and improved render data cleanup
Browse files Browse the repository at this point in the history
- Fix: Fixed render data computation optimization introduced in #54, commit 6137061.
- Add: Added *frame* parameter to `BaseImage._get_render_data()`.
- Add: Added tests for render data clean up.
- Change: PIL images are now closed much more agressively for better memory management.
- Change: Updated render data tests.
  • Loading branch information
AnonymouX47 committed Jun 24, 2022
1 parent 068e61b commit 4de17af
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 19 deletions.
7 changes: 4 additions & 3 deletions term_image/image/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,12 @@ def update_buffer():

bg_color = get_fg_bg_colors()[1]
width, height = self._get_render_size()
img, rgb, a = self._get_render_data(img, alpha, round_alpha=True)
frame_img = img if frame else None
img, rgb, a = self._get_render_data(img, alpha, round_alpha=True, frame=frame)
alpha = img.mode == "RGBA"

# clean up
if img is not self._source:
# clean up (ImageIterator uses one PIL image throughout)
if frame_img is not img is not self._source:
img.close()

rgb_pairs = (
Expand Down
31 changes: 23 additions & 8 deletions term_image/image/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1416,6 +1416,7 @@ def _get_render_data(
size: Optional[Tuple[int, int]] = None,
pixel_data: bool = True,
round_alpha: bool = False,
frame: bool = False,
) -> Tuple[
PIL.Image.Image, Optional[List[Tuple[int, int, int]]], Optional[List[int]]
]:
Expand All @@ -1432,6 +1433,9 @@ def _get_render_data(
Also, the image is blended with the active terminal's BG color (or black,
if undetermined) while leaving the alpha intact.
frame: If ``True``, implies *img* is being used by ``ImageIterator``,
hence, *img* is not closed.
The returned image is appropriately converted, resized and composited
(if need be).
Expand All @@ -1445,17 +1449,28 @@ def _get_render_data(

def convert_resize_img(mode: str):
nonlocal img
try:
if img.mode != mode:

if img.mode != mode:
prev_img = img
try:
img = img.convert(mode)
except Exception as e:
raise ValueError("Unable to convert image") from e
try:
if img.size != size:
except Exception as e:
raise ValueError("Unable to convert image") from e
finally:
if frame_img is not prev_img is not self._source:
prev_img.close()

if img.size != size:
prev_img = img
try:
img = img.resize(size, Image.Resampling.BOX)
except ValueError:
raise ValueError("Image size or scale too small") from None
except ValueError:
raise ValueError("Image size or scale too small") from None
finally:
if frame_img is not prev_img is not self._source:
prev_img.close()

frame_img = img if frame else None
if self._is_animated:
img.seek(self._seek_position)
if not size:
Expand Down
10 changes: 6 additions & 4 deletions term_image/image/iterm2.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,12 @@ def _render_image(
else self._source,
"rb",
)
frame_img = None
else:
frame_img = img if frame else None
img = self._get_render_data(
img, alpha, size=(width, height), pixel_data=False # fmt: skip
)[0]
img, alpha, size=(width, height), pixel_data=False, frame=frame
)[0] # fmt: skip
if self.JPEG_QUALITY >= 0 and img.mode == "RGB":
format = "jpeg"
jpeg_quality = min(self.JPEG_QUALITY, 95)
Expand All @@ -545,8 +547,8 @@ def _render_image(
quality=jpeg_quality,
)

# clean up
if img is not self._source:
# clean up (ImageIterator uses one PIL image throughout)
if frame_img is not img is not self._source:
img.close()

if render_method == LINES:
Expand Down
7 changes: 4 additions & 3 deletions term_image/image/kitty.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,14 +360,15 @@ def _render_image(
r_width, r_height = self.rendered_size
width, height = self._get_minimal_render_size()

frame_img = img if frame else None
img = self._get_render_data(
img, alpha, size=(width, height), pixel_data=False # fmt: skip
img, alpha, size=(width, height), pixel_data=False, frame=frame # fmt: skip
)[0]
format = getattr(f, img.mode)
raw_image = img.tobytes()

# clean up
if img is not self._source:
# clean up (ImageIterator uses one PIL image throughout)
if frame_img is not img is not self._source:
img.close()

control_data = ControlData(f=format, s=width, c=r_width, z=z_index)
Expand Down
26 changes: 26 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,32 @@ def test_font_ratio_adjustment(self):
set_font_ratio(0.5)


def test_render_clean_up_All():
img = Image.open("tests/images/python.png")
img_copy = img.copy()
img_image = ImageClass(img)
# Source
img_image._render_image(img, None)
img.load()
# Frame
img_image._render_image(img_copy, None, frame=True)
img_copy.load()
# Not source and not frame
img_image._render_image(img_copy, None)
with pytest.raises(ValueError, match="closed"):
img_copy.load()

file_image = ImageClass.from_file("tests/images/python.png")
file_img = file_image._get_image()
# Frame
img_image._render_image(file_img, None, frame=True)
file_img.load()
# Not source and not frame
file_image._render_image(file_img, None)
with pytest.raises(ValueError, match="closed"):
file_img.load()


def test_style_args_All():
image = ImageClass(python_img)
with pytest.raises(getattr(exceptions, f"{ImageClass.__name__}Error")):
Expand Down
4 changes: 3 additions & 1 deletion tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,6 @@ def test_small_size_scale(self):
self.trans.scale = 1.0

def test_alpha(self):
rgb_img = self.trans._get_image().convert("RGB")

# float
for alpha in (0.0, _ALPHA_THRESHOLD, 0.999):
Expand All @@ -518,6 +517,7 @@ def test_alpha(self):
assert img.mode == "RGBA"
assert all(px == 0 for px in a)

rgb_img = self.trans._get_image().convert("RGB")
img, _, a = self.get_render_data(rgb_img, alpha)
assert isinstance(img, Image.Image)
assert img.mode == "RGB"
Expand All @@ -530,6 +530,7 @@ def test_alpha(self):
assert img.mode == "RGB"
assert all(px == 255 for px in a)

rgb_img = self.trans._get_image().convert("RGB")
img, _, a = self.get_render_data(rgb_img, alpha)
assert isinstance(img, Image.Image)
assert img.mode == "RGB"
Expand All @@ -541,6 +542,7 @@ def test_alpha(self):
assert img.mode == "RGB"
assert all(px == 255 for px in a)

rgb_img = self.trans._get_image().convert("RGB")
img, _, a = self.get_render_data(rgb_img, None)
assert isinstance(img, Image.Image)
assert img.mode == "RGB"
Expand Down

0 comments on commit 4de17af

Please sign in to comment.