Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added gif editing feature #31

Merged
merged 9 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
local_test.py
main.py
ignored/
/tests/dev
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -140,4 +141,4 @@ dmypy.json
.pytype/

# Cython debug symbols
cython_debug/
cython_debug/
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
furo==2023.3.27
aiocache==0.12.1
aiohttp==3.8.4
Pillow==9.5.0
Pillow==10.1.0
requests==2.30.0
typing_extensions==4.5.0
3 changes: 2 additions & 1 deletion easy_pil/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def __init__(self, path: str, size: int = 10, **kwargs) -> None:
self.font = ImageFont.truetype(path, size=size, **kwargs)

def getsize(self, text: str):
return self.font.getsize(text)
bbox = self.font.getbbox(text)
return bbox[2], bbox[3]

@staticmethod
@lru_cache(32)
Expand Down
60 changes: 60 additions & 0 deletions easy_pil/gif_editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations

from io import BytesIO
from pathlib import Path
from typing import List, Tuple, Union

from PIL import Image as PilImage, ImageSequence
from PIL.GifImagePlugin import GifImageFile

from .editor import Editor


class GifEditor:
def __init__(self, image: Union[str, BytesIO, Path, GifImageFile]):
if isinstance(image, (str, BytesIO, Path)):
self.image = PilImage.open(image)
if isinstance(image, GifImageFile):
self.image = image

self.original_frames = ImageSequence.Iterator(self.image)
self.frames: List[Editor] = list(
map(lambda x: Editor(x), self.original_frames)
)
self.size: Tuple[int, int] = self.image.size

def __getattr__(self, name):
def wrapper(*args, **kwargs):
for frame in self.frames:
getattr(frame, name)(*args, **kwargs)

return wrapper

@property
def image_bytes(self) -> BytesIO:
"""Return image bytes

Returns
-------
BytesIO
Bytes from the image of Editor
"""
_bytes = BytesIO()
images = list(map(lambda e: e.image, self.frames))
images[0].save(_bytes, "GIF", save_all=True, append_images=images[1:])

_bytes.seek(0)
return _bytes

def save(self, fp, **kwargs):
"""Save the image

Parameters
----------
fp : str
File path
"""
images = list(map(lambda e: e.image, self.frames))
images[0].save(
fp, "GIF", save_all=True, append_images=images[1:], **kwargs
)
3 changes: 2 additions & 1 deletion easy_pil/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ def __init__(
self.font = font

def getsize(self):
return self.font.getsize(self.text)
bbox = self.font.getbbox(self.text)
return bbox[2], bbox[3]
25 changes: 21 additions & 4 deletions easy_pil/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import functools
from functools import lru_cache
from io import BytesIO
from typing import Optional, Union

import aiohttp
import requests
from aiocache import cached
from PIL import Image
from PIL.GifImagePlugin import GifImageFile


async def run_in_executor(func, **kwargs):
Expand All @@ -23,27 +25,37 @@ async def run_in_executor(func, **kwargs):


@lru_cache(maxsize=32)
def load_image(link: str) -> Image.Image:
def load_image(
link: str, raw: bool = False
) -> Union[Image.Image, GifImageFile]:
"""Load image from link

Parameters
----------
link : str
Image link
raw: bool
if you want the raw image without any conversion

Returns
-------
PIL.Image.Image
Image from the provided link (if any)
"""
_bytes = BytesIO(requests.get(link).content)
image = Image.open(_bytes).convert("RGBA")
image = Image.open(_bytes)
if not raw:
image = image.convert("RGBA")

return image


@cached(ttl=60 * 60 * 24)
async def load_image_async(link: str, session: aiohttp.ClientSession = None) -> Image.Image:
async def load_image_async(
link: str,
session: Optional[aiohttp.ClientSession] = None,
raw: bool = False,
) -> Union[Image.Image, GifImageFile]:
"""Load image from link (async)

Parameters
Expand All @@ -52,6 +64,8 @@ async def load_image_async(link: str, session: aiohttp.ClientSession = None) ->
Image from the provided link (if any)
session: aiohttp.ClientSession
clientSession for making requests, defaults to None
raw: bool
if you want the raw image without any conversion

Returns
-------
Expand All @@ -67,5 +81,8 @@ async def load_image_async(link: str, session: aiohttp.ClientSession = None) ->
data = await response.read()

_bytes = BytesIO(data)
image = Image.open(_bytes).convert("RGBA")
image = Image.open(_bytes)
if not raw:
image = image.convert("RGBA")

return image
4 changes: 2 additions & 2 deletions examples/text_bg_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
text_1 = "hello"
text_2 = "Your Name"

text_1_x, text_1_y = font_1.getsize(text_1)
text_2_x, text_2_y = font_2.getsize(text_2)
_, _, text_1_x, text_1_y = font_1.getbbox(text_1)
_, _, text_2_x, text_2_y = font_2.getbbox(text_2)

bg.rectangle((10, 10), text_1_x + 8, text_1_y + 5, "black")
bg.rectangle((10, 50), text_2_x + 8, text_2_y + 5, "black")
Expand Down
Loading