Skip to content

Commit

Permalink
feat: enable auto-png configuration (#1652)
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphaelVRossi committed Mar 4, 2024
1 parent 44f8644 commit 7e8c863
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 1 deletion.
13 changes: 12 additions & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ AUTO_*

These configurations indicates that thumbor will try to automatically convert
the image format to a lighter image format, according to this compression order:
`WEBP, AVIF, JPG, HEIF` — from highest (`WEBP`) to lowest (`HEIF`) priority.
`WEBP, AVIF, JPG, HEIF, PNG` — from highest (`WEBP`) to lowest (`PNG`) priority.

AUTO\_WEBP
^^^^^^^^^^
Expand Down Expand Up @@ -401,6 +401,17 @@ specifies that the browser supports "*/*", "image/jpg" or "image/jpeg".
AUTO_JPG = True
AUTO\_PNG
^^^^^^^^^

This option indicates whether thumbor should send PNG images
automatically if the request comes with an "Accept" header that
specifies that the browser supports "image/png".

.. code:: python
AUTO_PNG = True
AUTO\_HEIF
^^^^^^^^^^

Expand Down
127 changes: 127 additions & 0 deletions tests/handlers/test_base_handler_with_auto_png.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# -*- 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) 2024 globo.com thumbor@g.globo

from shutil import which

from preggy import expect
from tornado.testing import gen_test

from tests.handlers.test_base_handler import BaseImagingTestCase
from thumbor.config import Config
from thumbor.context import Context, ServerParameters
from thumbor.importer import Importer

# pylint: disable=broad-except,abstract-method,attribute-defined-outside-init,line-too-long,too-many-public-methods
# pylint: disable=too-many-lines

MIMETYPE = "image/png"


class ImageOperationsWithAutoPngTestCase(BaseImagingTestCase):
def get_context(self):
cfg = Config(SECURITY_KEY="ACME-SEC")
cfg.LOADER = "thumbor.loaders.file_loader"
cfg.FILE_LOADER_ROOT_PATH = self.loader_path
cfg.STORAGE = "thumbor.storages.no_storage"
cfg.AUTO_PNG = True

importer = Importer(cfg)
importer.import_modules()
server = ServerParameters(
8889, "localhost", "thumbor.conf", None, "info", None
)
server.security_key = "ACME-SEC"
ctx = Context(server, cfg, importer)
ctx.server.gifsicle_path = which("gifsicle")
return ctx

async def get_as_png(self, url):
return await self.async_fetch(
url, headers={"Accept": f"{MIMETYPE},*/*;q=0.8"}
)

@gen_test
async def test_can_auto_convert_jpeg_to_png(self):
response = await self.get_as_png("/unsafe/image.jpg")

expect(response.code).to_equal(200)
expect(response.headers["Content-type"]).to_equal(MIMETYPE)
expect(response.body).to_be_png()

@gen_test
async def test_can_auto_convert_heic_to_png(self):
response = await self.get_as_png("/unsafe/image.heic")

expect(response.code).to_equal(200)
expect(response.headers["Content-type"]).to_equal(MIMETYPE)
expect(response.body).to_be_png()

@gen_test
async def test_should_not_convert_animated_gifs_to_png(self):
response = await self.get_as_png("/unsafe/animated.gif")

expect(response.code).to_equal(200)
expect(response.body).to_be_gif()

@gen_test
async def test_should_convert_image_with_small_width_and_no_height_to_png(
self,
):
response = await self.get_as_png("/unsafe/0x0:1681x596/1x/image.jpg")

expect(response.code).to_equal(200)
expect(response.headers["Content-type"]).to_equal(MIMETYPE)
expect(response.body).to_be_png()

@gen_test
async def test_should_convert_monochromatic_jpeg_to_png(self):
response = await self.get_as_png("/unsafe/grayscale.jpg")
expect(response.code).to_equal(200)
expect(response.headers["Content-type"]).to_equal(MIMETYPE)
expect(response.body).to_be_png()

@gen_test
async def test_should_convert_cmyk_jpeg_to_png(self):
response = await self.get_as_png("/unsafe/cmyk.jpg")
expect(response.code).to_equal(200)
expect(response.headers["Content-type"]).to_equal(MIMETYPE)
expect(response.body).to_be_png()

@gen_test
async def test_shouldnt_convert_to_cmyk_to_png_if_format_specified(self):
response = await self.get_as_png(
"/unsafe/filters:format(png)/cmyk.jpg"
)
expect(response.code).to_equal(200)
expect(response.body).to_be_png()

@gen_test
async def test_shouldnt_convert_cmyk_to_png_if_gif(self):
response = await self.get_as_png(
"/unsafe/filters:format(gif)/cmyk.jpg"
)
expect(response.code).to_equal(200)
expect(response.body).to_be_gif()

@gen_test
async def test_shouldnt_convert_gif_to_png_if_format_specified(self):
response = await self.get_as_png(
"/unsafe/filters:format(gif)/image.jpg"
)
expect(response.code).to_equal(200)
expect(response.body).to_be_gif()

@gen_test
async def test_should_convert_to_png_if_format_invalid(self):
response = await self.get_as_png(
"/unsafe/filters:format(asdf)/image.jpg"
)
expect(response.code).to_equal(200)
expect(response.headers["Content-type"]).to_equal(MIMETYPE)
expect(response.body).to_be_png()
7 changes: 7 additions & 0 deletions thumbor/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@
"(via Accept header) and pillow-heif is enabled",
"Imaging",
)
Config.define(
"AUTO_PNG",
False,
"Specifies whether PNG format should be used automatically if the request accepts it "
"(via Accept header)",
"Imaging",
)
Config.define(
"AUTO_PNG_TO_JPG",
False,
Expand Down
18 changes: 18 additions & 0 deletions thumbor/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,19 @@ def can_auto_convert_to_jpg(self):

return False

def can_auto_convert_to_png(self):
auto_png = self.context.config.AUTO_PNG
accepts_png = self.accepts_mime_type("image/png")

if (
auto_png
and accepts_png
and not self.context.request.engine.is_multiple()
):
return True

return False

def define_image_type(self, context, result):
if result is not None:
if isinstance(result, ResultStorageResult):
Expand Down Expand Up @@ -509,6 +522,11 @@ def define_image_type(self, context, result):
logger.debug(
"Image format set by AUTO_HEIF as %s.", image_extension
)
elif self.can_auto_convert_to_png():
image_extension = ".png"
logger.debug(
"Image format set by AUTO_PNG as %s.", image_extension
)
else:
image_extension = context.request.engine.extension
logger.debug(
Expand Down
3 changes: 3 additions & 0 deletions thumbor/thumbor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ home = expanduser("~")
## Automatically converts images to JPG if Accepts header present
# AUTO_JPG = False

## Automatically converts images to PNG if Accepts header present
# AUTO_PNG = False
#
## Automatically converts images to Avif if Accepts header present and
## pillow-avif-plugin is enabled
# AUTO_AVIF = False
Expand Down

0 comments on commit 7e8c863

Please sign in to comment.