This PR aims to add `Vary` header in Thumbor response to avoid problems with downstream caches.

Closes https://github.com/thumbor/thumbor/issues/1563
thanks @mpdude 
Should have vary header with auto heif and avif. This PR aims to add `Vary` header in Thumbor response to avoid problems with downstream caches.

Closes https://github.com/thumbor/thumbor/issues/1563
thanks @mpdude 
#!/usr/bin/python
# -*- coding: utf-8 -*-

# thumbor imaging service
# https://github.com/thumbor/thumbor/wiki

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2011 globo.com thumbor@googlegroups.com

import json

from thumbor.engines import BaseEngine


class JSONEngine(BaseEngine):
    def __init__(self, engine, path, callback_name=None):
        super().__init__(engine.context)
        self.engine = engine
        self.extension = engine.extension
        self.path = path
        self.callback_name = callback_name
        self.operations = []
        self.focal_points = []
        self.refresh_image()
        self.exif = getattr(engine, "exif", None)

    def refresh_image(self):
        self.image = self.engine.image

    @property
    def size(self):
        return self.engine.size

    def resize(self, width, height):
        self.operations.append(
            {"type": "resize", "width": width, "height": height}
        )
        self.engine.resize(width, height)
        self.refresh_image()

    def crop(self, left, top, right, bottom):
        self.operations.append(
            {
                "type": "crop",
                "left": left,
                "top": top,
                "right": right,
                "bottom": bottom,
            }
        )
        self.engine.crop(left, top, right, bottom)
        self.refresh_image()

    def focus(self, points):
        for point in points:
            self.focal_points.append(point.to_dict())

    def flip_vertically(self):
        self.operations.append({"type": "flip_vertically"})

    def flip_horizontally(self):
        self.operations.append({"type": "flip_horizontally"})

    def get_target_dimensions(self):
        width, height = self.engine.size
        for operation in self.operations:
            if operation["type"] == "crop":
                width = operation["right"] - operation["left"]
                height = operation["bottom"] - operation["top"]

            if operation["type"] == "resize":
                width = operation["width"]
                height = operation["height"]

        return (width, height)

    def gen_image(self, size, color):
        return self.engine.gen_image(size, color)

    def create_image(self, buffer):
        return self.engine.create_image(buffer)

    def draw_rectangle(self, x, y, width, height):
        return self.engine.draw_rectangle(x, y, width, height)

    def rotate(self, degrees):
        return self.engine.rotate(degrees)

    def read_multiple(self, images, extension=None):
        return self.engine.read_multiple(images, extension)

    def paste(self, other_engine, pos, merge=True):
        return self.engine.paste(other_engine, pos, merge)

    def enable_alpha(self):
        return self.engine.enable_alpha()

    def strip_exif(self):
        return self.engine.strip_exif()

    def strip_icc(self):
        return self.engine.strip_icc()

    def get_image_mode(self):
        return self.engine.get_image_mode()

    def get_image_data(self):
        return self.engine.get_image_data()

    def set_image_data(self, data):
        return self.engine.set_image_data(data)

    def image_data_as_rgb(self, update_image=True):
        return self.engine.image_data_as_rgb(update_image)

    def convert_to_grayscale(self, update_image=True, alpha=True):
        pass

    def get_frame_count(self):
        return self.engine.frame_count

    def has_transparency(self):
        return self.engine.has_transparency()

    def avif_enabled(self):
        return self.engine.avif_enabled()

    def heif_enabled(self):
        return self.engine.heif_enabled()

    def can_auto_convert_png_to_jpg(self):
        can_convert = super().can_auto_convert_png_to_jpg()
        if can_convert:
            self.operations.append({"type": "auto_png_to_jpg_conversion"})

    def read(self, extension, quality):
        target_width, target_height = self.get_target_dimensions()
        width, height = self.engine.size
        thumbor_json = {
            "thumbor": {
                "source": {
                    "url": self.path,
                    "width": width,
                    "height": height,
                    "frameCount": self.get_frame_count(),
                },
                "operations": self.operations,
                "target": {"width": target_width, "height": target_height},
            }
        }

        if self.focal_points:
            thumbor_json["thumbor"]["focal_points"] = self.focal_points

        thumbor_json = json.dumps(thumbor_json)

        if self.callback_name:
            return f"{self.callback_name}({thumbor_json});"

        return thumbor_json

#!/usr/bin/python
# -*- coding: utf-8 -*-

# thumbor imaging service
# https://github.com/thumbor/thumbor/wiki

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2011 globo.com thumbor@googlegroups.com

import json

from thumbor.engines import BaseEngine


class JSONEngine(BaseEngine):
    def __init__(self, engine, path, callback_name=None):
        super().__init__(engine.context)
        self.engine = engine
        self.extension = engine.extension
        self.path = path
        self.callback_name = callback_name
        self.operations = []
        self.focal_points = []
        self.refresh_image()
        self.exif = getattr(engine, "exif", None)

    def refresh_image(self):
        self.image = self.engine.image

    @property
    def size(self):
        return self.engine.size

    def resize(self, width, height):
        self.operations.append(
            {"type": "resize", "width": width, "height": height}
        )
        self.engine.resize(width, height)
        self.refresh_image()

    def crop(self, left, top, right, bottom):
        self.operations.append(
            {
                "type": "crop",
                "left": left,
                "top": top,
                "right": right,
                "bottom": bottom,
            }
        )
        self.engine.crop(left, top, right, bottom)
        self.refresh_image()

    def focus(self, points):
        for point in points:
            self.focal_points.append(point.to_dict())

    def flip_vertically(self):
        self.operations.append({"type": "flip_vertically"})

    def flip_horizontally(self):
        self.operations.append({"type": "flip_horizontally"})

    def get_target_dimensions(self):
        width, height = self.engine.size
        for operation in self.operations:
            if operation["type"] == "crop":
                width = operation["right"] - operation["left"]
                height = operation["bottom"] - operation["top"]

            if operation["type"] == "resize":
                width = operation["width"]
                height = operation["height"]

        return (width, height)

    def gen_image(self, size, color):
        return self.engine.gen_image(size, color)

    def create_image(self, buffer):
        return self.engine.create_image(buffer)

    def draw_rectangle(self, x, y, width, height):
        return self.engine.draw_rectangle(x, y, width, height)

    def rotate(self, degrees):
        return self.engine.rotate(degrees)

    def read_multiple(self, images, extension=None):
        return self.engine.read_multiple(images, extension)

    def paste(self, other_engine, pos, merge=True):
        return self.engine.paste(other_engine, pos, merge)

    def enable_alpha(self):
        return self.engine.enable_alpha()

    def strip_exif(self):
        return self.engine.strip_exif()

    def strip_icc(self):
        return self.engine.strip_icc()

    def get_image_mode(self):
        return self.engine.get_image_mode()

    def get_image_data(self):
        return self.engine.get_image_data()

    def set_image_data(self, data):
        return self.engine.set_image_data(data)

    def image_data_as_rgb(self, update_image=True):
        return self.engine.image_data_as_rgb(update_image)

    def convert_to_grayscale(self, update_image=True, alpha=True):
        pass

    def get_frame_count(self):
        return self.engine.frame_count

    def has_transparency(self):
        return self.engine.has_transparency()

    def avif_enabled(self):
        return self.engine.avif_enabled()

    def heif_enabled(self):
        return self.engine.heif_enabled()

    def can_auto_convert_png_to_jpg(self):
        can_convert = super().can_auto_convert_png_to_jpg()
        if can_convert:
            self.operations.append({"type": "auto_png_to_jpg_conversion"})

    def read(self, extension, quality):
        target_width, target_height = self.get_target_dimensions()
        width, height = self.engine.size
        thumbor_json = {
            "thumbor": {
                "source": {
                    "url": self.path,
                    "width": width,
                    "height": height,
                    "frameCount": self.get_frame_count(),
                },
                "operations": self.operations,
                "target": {"width": target_width, "height": target_height},
            }
        }

        if self.focal_points:
            thumbor_json["thumbor"]["focal_points"] = self.focal_points

        thumbor_json = json.dumps(thumbor_json)

        if self.callback_name:
            return f"{self.callback_name}({thumbor_json});"

        return thumbor_json
