Skip to content

Commit

Permalink
Merge pull request #31 from shahriyardx/feat/gif
Browse files Browse the repository at this point in the history
added gif editing feature
  • Loading branch information
shahriyardx authored Dec 1, 2023
2 parents 365c573 + 947f4bd commit ce49e38
Show file tree
Hide file tree
Showing 12 changed files with 674 additions and 614 deletions.
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

0 comments on commit ce49e38

Please sign in to comment.