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

respondcreate / django-versatileimagefield / 7602120718

21 Jan 2024 03:19PM UTC coverage: 99.777%. First build
7602120718

push

github

respondcreate
Merge branch 'alexei-fix-pil_antialias' into github-actions-fork-pr-fix

2 of 4 new or added lines in 1 file covered. (50.0%)

893 of 895 relevant lines covered (99.78%)

31.9 hits per line

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

100.0
/versatileimagefield/datastructures/base.py
1
"""Base datastructures for manipulated images."""
2
from PIL import Image
32✔
3

4
from django.core.files.uploadedfile import InMemoryUploadedFile
32✔
5

6
from ..settings import (
32✔
7
    JPEG_QUAL,
8
    VERSATILEIMAGEFIELD_PROGRESSIVE_JPEG,
9
    VERSATILEIMAGEFIELD_LOSSLESS_WEBP,
10
    WEBP_QUAL,
11
)
12
from ..utils import get_image_metadata_from_file
32✔
13

14
EXIF_ORIENTATION_KEY = 274
32✔
15

16

17
class ProcessedImage(object):
32✔
18
    """
19
    A base class for processing/saving different renditions of an image.
20

21
    Constructor arguments:
22
        * `path_to_image`: A path to a file within `storage`
23
        * `storage`: A django storage class
24
        * `create_on_demand`: A bool signifying whether new images should be
25
                              created on-demand.
26

27
    Subclasses must define the `process_image` method. see
28
    versatileimagefield.datastructures.filteredimage.FilteredImage and
29
    versatileimagefield.datastructures.sizedimage.SizedImage
30
    for examples.
31

32
    Includes a preprocessing API based on image format/file type. See
33
    the `preprocess` method for more specific information.
34
    """
35

36
    name = None
32✔
37
    url = None
32✔
38

39
    def __init__(self, path_to_image, storage, create_on_demand,
32✔
40
                 placeholder_image=None):
41
        """Construct a ProcessedImage."""
42
        self.path_to_image = path_to_image
32✔
43
        self.storage = storage
32✔
44
        self.create_on_demand = create_on_demand
32✔
45
        self.placeholder_image = placeholder_image
32✔
46

47
    def process_image(self, image, image_format, **kwargs):
32✔
48
        """
49
        Ensure NotImplemented is raised if not overloaded by subclasses.
50

51
        Arguments:
52
            * `image`: a PIL Image instance
53
            * `image_format`: str, a valid PIL format (i.e. 'JPEG' or 'GIF')
54

55
        Returns a BytesIO representation of the resized image.
56

57
        Subclasses MUST implement this method.
58
        """
59
        raise NotImplementedError(
32✔
60
            'Subclasses MUST provide a `process_image` method.'
61
        )
62

63
    def preprocess(self, image, image_format):
32✔
64
        """
65
        Preprocess an image.
66

67
        An API hook for image pre-processing. Calls any image format specific
68
        pre-processors (if defined). I.E. If `image_format` is 'JPEG', this
69
        method will look for a method named `preprocess_JPEG`, if found
70
        `image` will be passed to it.
71

72
        Arguments:
73
            * `image`: a PIL Image instance
74
            * `image_format`: str, a valid PIL format (i.e. 'JPEG' or 'GIF')
75

76
        Subclasses should return a 2-tuple:
77
            * [0]: A PIL Image instance.
78
            * [1]: A dictionary of additional keyword arguments to be used
79
                   when the instance is saved. If no additional keyword
80
                   arguments, return an empty dict ({}).
81
        """
82
        save_kwargs = {'format': image_format}
32✔
83

84
        # Ensuring image is properly rotated
85
        if hasattr(image, '_getexif'):
32✔
86
            exif_datadict = image._getexif()  # returns None if no EXIF data
32✔
87
            if exif_datadict is not None:
32✔
88
                exif = dict(exif_datadict.items())
32✔
89
                orientation = exif.get(EXIF_ORIENTATION_KEY, None)
32✔
90
                if orientation == 3:
32✔
91
                    image = image.transpose(Image.ROTATE_180)
32✔
92
                elif orientation == 6:
32✔
93
                    image = image.transpose(Image.ROTATE_270)
32✔
94
                elif orientation == 8:
32✔
95
                    image = image.transpose(Image.ROTATE_90)
32✔
96

97
        # Ensure any embedded ICC profile is preserved
98
        save_kwargs['icc_profile'] = image.info.get('icc_profile')
32✔
99

100
        if hasattr(self, 'preprocess_%s' % image_format):
32✔
101
            image, addl_save_kwargs = getattr(
32✔
102
                self,
103
                'preprocess_%s' % image_format
104
            )(image=image)
105
            save_kwargs.update(addl_save_kwargs)
32✔
106

107
        return image, save_kwargs
32✔
108

109
    def preprocess_GIF(self, image, **kwargs):
32✔
110
        """
111
        Receive a PIL Image instance of a GIF and return 2-tuple.
112

113
        Args:
114
            * [0]: Original Image instance (passed to `image`)
115
            * [1]: Dict with a transparency key (to GIF transparency layer)
116
        """
117
        if 'transparency' in image.info:
32✔
118
            save_kwargs = {'transparency': image.info['transparency']}
32✔
119
        else:
120
            save_kwargs = {}
32✔
121
        return (image, save_kwargs)
32✔
122

123
    def preprocess_JPEG(self, image, **kwargs):
32✔
124
        """
125
        Receive a PIL Image instance of a JPEG and returns 2-tuple.
126

127
        Args:
128
            * [0]: Image instance, converted to RGB
129
            * [1]: Dict with a quality key (mapped to the value of `JPEG_QUAL`
130
                   defined by the `VERSATILEIMAGEFIELD_JPEG_RESIZE_QUALITY`
131
                   setting)
132
        """
133
        save_kwargs = {
32✔
134
            'progressive': VERSATILEIMAGEFIELD_PROGRESSIVE_JPEG,
135
            'quality': JPEG_QUAL
136
        }
137
        if image.mode != 'RGB':
32✔
138
            image = image.convert('RGB')
32✔
139
        return (image, save_kwargs)
32✔
140

141
    def preprocess_WEBP(self, image, **kwargs):
32✔
142
        """
143
        Receive a PIL Image instance of a WEBP and return 2-tuple.
144

145
        Args:
146
            * [0]: Original Image instance (passed to `image`)
147
            * [1]: Dict with a quality key (mapped to the value of `WEBP_QUAL`
148
                   as defined by the `VERSATILEIMAGEFIELD_RESIZE_QUALITY`
149
                   setting)
150
        """
151
        save_kwargs = {
32✔
152
            "quality": WEBP_QUAL,
153
            "lossless": VERSATILEIMAGEFIELD_LOSSLESS_WEBP,
154
            "icc_profile": image.info.get('icc_profile', '')
155
        }
156

157
        return (image, save_kwargs)
32✔
158

159
    def retrieve_image(self, path_to_image):
32✔
160
        """Return a PIL Image instance stored at `path_to_image`."""
161
        image = self.storage.open(path_to_image, 'rb')
32✔
162
        image_format, mime_type = get_image_metadata_from_file(image)
32✔
163
        file_ext = path_to_image.rsplit('.')[-1]
32✔
164

165
        return (
32✔
166
            Image.open(image),
167
            file_ext,
168
            image_format,
169
            mime_type
170
        )
171

172
    def save_image(self, imagefile, save_path, file_ext, mime_type):
32✔
173
        """
174
        Save an image to self.storage at `save_path`.
175

176
        Arguments:
177
            `imagefile`: Raw image data, typically a BytesIO instance.
178
            `save_path`: The path within self.storage where the image should
179
                         be saved.
180
            `file_ext`: The file extension of the image-to-be-saved.
181
            `mime_type`: A valid image mime type (as found in
182
                         versatileimagefield.utils)
183
        """
184
        file_to_save = InMemoryUploadedFile(
32✔
185
            imagefile,
186
            None,
187
            'foo.%s' % file_ext,
188
            mime_type,
189
            imagefile.tell(),
190
            None
191
        )
192
        file_to_save.seek(0)
32✔
193
        self.storage.save(save_path, file_to_save)
32✔
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