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

respondcreate / django-versatileimagefield / 7602425340

21 Jan 2024 04:17PM UTC coverage: 99.777%. Remained the same
7602425340

push

github

web-flow
Merge pull request #205 from respondcreate/github-actions-fork-pr-fix

GitHub actions fork pr fix & Django `5.0.x` Added To Test Matrix

893 of 895 relevant lines covered (99.78%)

9.97 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
10✔
3

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

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

14
EXIF_ORIENTATION_KEY = 274
10✔
15

16

17
class ProcessedImage(object):
10✔
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
10✔
37
    url = None
10✔
38

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

47
    def process_image(self, image, image_format, **kwargs):
10✔
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(
10✔
60
            'Subclasses MUST provide a `process_image` method.'
61
        )
62

63
    def preprocess(self, image, image_format):
10✔
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}
10✔
83

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

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

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

107
        return image, save_kwargs
10✔
108

109
    def preprocess_GIF(self, image, **kwargs):
10✔
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:
10✔
118
            save_kwargs = {'transparency': image.info['transparency']}
10✔
119
        else:
120
            save_kwargs = {}
10✔
121
        return (image, save_kwargs)
10✔
122

123
    def preprocess_JPEG(self, image, **kwargs):
10✔
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 = {
10✔
134
            'progressive': VERSATILEIMAGEFIELD_PROGRESSIVE_JPEG,
135
            'quality': JPEG_QUAL
136
        }
137
        if image.mode != 'RGB':
10✔
138
            image = image.convert('RGB')
10✔
139
        return (image, save_kwargs)
10✔
140

141
    def preprocess_WEBP(self, image, **kwargs):
10✔
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 = {
10✔
152
            "quality": WEBP_QUAL,
153
            "lossless": VERSATILEIMAGEFIELD_LOSSLESS_WEBP,
154
            "icc_profile": image.info.get('icc_profile', '')
155
        }
156

157
        return (image, save_kwargs)
10✔
158

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

165
        return (
10✔
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):
10✔
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(
10✔
185
            imagefile,
186
            None,
187
            'foo.%s' % file_ext,
188
            mime_type,
189
            imagefile.tell(),
190
            None
191
        )
192
        file_to_save.seek(0)
10✔
193
        self.storage.save(save_path, file_to_save)
10✔
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