• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LeanderCS / flask-inputfilter / #72

18 Jan 2025 10:38PM UTC coverage: 98.065% (-1.2%) from 99.252%
#72

push

coveralls-python

web-flow
Merge pull request #21 from LeanderCS/17

17 | Add new filters and validators

156 of 173 new or added lines in 11 files covered. (90.17%)

1216 of 1240 relevant lines covered (98.06%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.19
/flask_inputfilter/Filter/Base64ImageResizeFilter.py
1
import base64
1✔
2
import io
1✔
3
from typing import Any
1✔
4

5
from PIL import Image
1✔
6

7
from ..Enum import ImageFormatEnum
1✔
8
from .BaseFilter import BaseFilter
1✔
9

10

11
class Base64ImageResizeFilter(BaseFilter):
1✔
12
    """
13
    A filter to reduce the file size of a base64-encoded
14
    image by resizing and compressing it.
15
    """
16

17
    def __init__(
1✔
18
        self,
19
        max_size: int = 4 * 1024 * 1024,
20
        format: ImageFormatEnum = ImageFormatEnum.JPEG,
21
        preserve_icc_profile: bool = False,
22
        preserve_metadata: bool = False,
23
    ) -> None:
24
        self.max_size = max_size
1✔
25
        self.format = format
1✔
26
        self.preserve_metadata = preserve_metadata
1✔
27
        self.preserve_icc_profile = preserve_icc_profile
1✔
28

29
    def apply(self, value: Any) -> Any:
1✔
30
        if not isinstance(value, (str, Image.Image)):
1✔
NEW
31
            return value
×
32

33
        try:
1✔
34
            if isinstance(value, Image.Image):
1✔
35
                return self.reduce_image(value)
1✔
36

37
            value = Image.open(io.BytesIO(base64.b64decode(value)))
1✔
38
            return self.reduce_image(value)
1✔
NEW
39
        except Exception:
×
NEW
40
            return value
×
41

42
    def reduce_image(self, image: Image) -> Image:
1✔
43
        """Reduce the size of an image by resizing and compressing it."""
44
        is_animated = getattr(image, "is_animated", False)
1✔
45

46
        if not is_animated and image.mode in ("RGBA", "P"):
1✔
47
            image = image.convert("RGB")
1✔
48

49
        buffer = self.save_image_to_buffer(image, quality=80)
1✔
50
        if buffer.getbuffer().nbytes <= self.max_size:
1✔
NEW
51
            return self.image_to_base64(image)
×
52

53
        new_width, new_height = image.size
1✔
54

55
        while (
1✔
56
            buffer.getbuffer().nbytes > self.max_size
57
            and new_width > 10
58
            and new_height > 10
59
        ):
60
            new_width = int(new_width * 0.9)
1✔
61
            new_height = int(new_height * 0.9)
1✔
62
            image = image.resize((new_width, new_height), Image.LANCZOS)
1✔
63

64
            buffer = self.save_image_to_buffer(image, quality=80)
1✔
65

66
        quality = 80
1✔
67
        while buffer.getbuffer().nbytes > self.max_size and quality > 0:
1✔
NEW
68
            buffer = self.save_image_to_buffer(image, quality)
×
NEW
69
            quality -= 5
×
70

71
        return self.image_to_base64(image)
1✔
72

73
    def save_image_to_buffer(
1✔
74
        self, image: Image.Image, quality: int
75
    ) -> io.BytesIO:
76
        """Save the image to an in-memory buffer with the specified quality."""
77
        buffer = io.BytesIO()
1✔
78
        image.save(buffer, format=self.format.value, quality=quality)
1✔
79
        buffer.seek(0)
1✔
80
        return buffer
1✔
81

82
    def image_to_base64(self, image: Image) -> str:
1✔
83
        """Convert an image to a base64-encoded string."""
84
        buffered = io.BytesIO()
1✔
85
        options = {
1✔
86
            "format": self.format.value,
87
            "optimize": True,
88
        }
89
        if self.preserve_icc_profile:
1✔
NEW
90
            options["icc_profile"] = image.info.get("icc_profile", None)
×
91
        if self.preserve_metadata:
1✔
NEW
92
            options["exif"] = image.info.get("exif", None)
×
93
        image.save(buffered, **options)
1✔
94
        return base64.b64encode(buffered.getvalue()).decode("ascii")
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc